fix ghastly deadlock/crash during exit
[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 <QBitmap>
34 #include <QCheckBox>
35 #include <QDialog>
36 #include <QImage>
37 #include <QHBoxLayout>
38 #include <QVBoxLayout>
39 #include <QPainter>
40 #include <QMouseEvent>
41 #include <QPushButton>
42 #include <QEvent>
43 #include <QSettings>
44 #include <QTime>
45 #include <QTimer>
46
47
48 // Definition of key modifier mask constants
49 #define KMOD_Alt_R      0x01
50 #define KMOD_Alt_L      0x02
51 #define KMOD_Meta_L     0x04
52 #define KMOD_Control_L  0x08
53 #define KMOD_Shift_L    0x10
54
55 //local cursor width/height in px, should be an odd number
56 const int cursor_size = 7;
57
58 VncView::VncView(QWidget *parent, const KUrl &url, RemoteView::Quality quality)
59         : RemoteView(parent),
60         m_initDone(false),
61         m_buttonMask(0),
62         cursor_x(0),
63         cursor_y(0),
64         m_repaint(false),
65         m_quitFlag(false),
66         m_firstPasswordTry(true),
67         m_dontSendClipboard(false),
68         m_horizontalFactor(1.0),
69         m_verticalFactor(1.0),
70         m_forceLocalCursor(false),
71         force_full_repaint(false),
72         quality(quality)
73 {
74     m_url = url;
75     m_host = url.host();
76     m_port = url.port();
77
78         //BlockingQueuedConnection can cause deadlocks when exiting, handled in startQuitting()
79     connect(&vncThread, SIGNAL(imageUpdated(int, int, int, int)), this, SLOT(updateImage(int, int, int, int)), Qt::BlockingQueuedConnection);
80     connect(&vncThread, SIGNAL(gotCut(const QString&)), this, SLOT(setCut(const QString&)), Qt::BlockingQueuedConnection);
81     connect(&vncThread, SIGNAL(passwordRequest()), this, SLOT(requestPassword()), Qt::BlockingQueuedConnection);
82     connect(&vncThread, SIGNAL(outputErrorMessage(QString)), this, SLOT(outputErrorMessage(QString)));
83
84     m_clipboard = QApplication::clipboard();
85     connect(m_clipboard, SIGNAL(selectionChanged()), this, SLOT(clipboardSelectionChanged()));
86     connect(m_clipboard, SIGNAL(dataChanged()), this, SLOT(clipboardDataChanged()));
87
88     reloadSettings();
89 }
90
91 VncView::~VncView()
92 {
93     unpressModifiers();
94
95     // Disconnect all signals so that we don't get any more callbacks from the client thread
96     vncThread.disconnect();
97
98     startQuitting();
99 }
100
101 void VncView::forceFullRepaint()
102 {
103         force_full_repaint = true;
104         repaint();
105 }
106
107 bool VncView::eventFilter(QObject *obj, QEvent *event)
108 {
109     if (m_viewOnly) {
110         if (event->type() == QEvent::KeyPress ||
111                 event->type() == QEvent::KeyRelease ||
112                 event->type() == QEvent::MouseButtonDblClick ||
113                 event->type() == QEvent::MouseButtonPress ||
114                 event->type() == QEvent::MouseButtonRelease ||
115                 event->type() == QEvent::Wheel ||
116                 event->type() == QEvent::MouseMove)
117             return true;
118     }
119     return RemoteView::eventFilter(obj, event);
120 }
121
122 QSize VncView::framebufferSize()
123 {
124     return m_frame.size();
125 }
126
127 QSize VncView::sizeHint() const
128 {
129     return size();
130 }
131
132 QSize VncView::minimumSizeHint() const
133 {
134     return size();
135 }
136
137 void VncView::startQuitting()
138 {
139     kDebug(5011) << "about to quit";
140
141     //const bool connected = status() == RemoteView::Connected;
142
143     setStatus(Disconnecting);
144
145     m_quitFlag = true;
146
147         //if(connected) //remove if things work without it
148         vncThread.stop();
149
150     const bool quitSuccess = vncThread.wait(700);
151         if(!quitSuccess) {
152                 //happens when vncThread wants to call a slot via BlockingQueuedConnection,
153                 //needs an event loop in this thread so execution continues after 'emit'
154                 QEventLoop loop;
155                 if(!loop.processEvents())
156                         kDebug(5011) << "BUG: deadlocked, but no events to deliver?";
157                 vncThread.wait(700);
158         }
159     setStatus(Disconnected);
160 }
161
162 bool VncView::isQuitting()
163 {
164     return m_quitFlag;
165 }
166
167 bool VncView::start()
168 {
169     vncThread.setHost(m_host);
170     vncThread.setPort(m_port);
171
172     vncThread.setQuality(quality);
173
174     // set local cursor on by default because low quality mostly means slow internet connection
175     if (quality == RemoteView::Low) {
176         showDotCursor(RemoteView::CursorOn);
177     }
178
179     setStatus(Connecting);
180
181     vncThread.start();
182     return true;
183 }
184
185 bool VncView::supportsScaling() const
186 {
187     return true;
188 }
189
190 bool VncView::supportsLocalCursor() const
191 {
192     return true;
193 }
194
195 void VncView::requestPassword()
196 {
197     kDebug(5011) << "request password";
198
199     setStatus(Authenticating);
200
201     if (!m_url.password().isNull()) {
202         vncThread.setPassword(m_url.password());
203         return;
204     }
205
206         QSettings settings;
207         settings.beginGroup("hosts");
208         QString password = settings.value(QString("%1/password").arg(m_host), "").toString();
209         //check for saved password
210         if(m_firstPasswordTry and !password.isEmpty()) {
211                 kDebug(5011) << "Trying saved password";
212                 m_firstPasswordTry = false;
213                 vncThread.setPassword(password);
214                 return;
215         }
216         m_firstPasswordTry = false;
217
218         //build dialog
219         QDialog dialog(this);
220         dialog.setWindowTitle(tr("Password required"));
221
222         QLineEdit passwordbox;
223         passwordbox.setEchoMode(QLineEdit::Password);
224         passwordbox.setText(password);
225         QCheckBox save_password(tr("Save Password"));
226         save_password.setChecked(!password.isEmpty()); //offer to overwrite saved password
227         QPushButton ok_button(tr("Done"));
228         ok_button.setMaximumWidth(100);
229         connect(&ok_button, SIGNAL(clicked()),
230                 &dialog, SLOT(accept()));
231
232         QHBoxLayout layout1;
233         QVBoxLayout layout2;
234         layout2.addWidget(&passwordbox);
235         layout2.addWidget(&save_password);
236         layout1.addLayout(&layout2);
237         layout1.addWidget(&ok_button);
238         dialog.setLayout(&layout1);
239
240         if(dialog.exec()) { //dialog accepted
241                 password = passwordbox.text();
242
243                 if(save_password.isChecked()) {
244                         kDebug(5011) << "Saving password for host '" << m_host << "'";
245
246                         settings.setValue(QString("%1/password").arg(m_host), password);
247                         settings.sync();
248                 }
249
250                 vncThread.setPassword(password);
251         } else {
252                 startQuitting();
253         }
254 }
255
256 void VncView::outputErrorMessage(const QString &message)
257 {
258     kDebug(5011) << message;
259
260     if (message == "INTERNAL:APPLE_VNC_COMPATIBILTY") {
261         setCursor(localDotCursor());
262         m_forceLocalCursor = true;
263         return;
264     }
265
266     startQuitting();
267
268     emit errorMessage(i18n("VNC failure"), message);
269 }
270
271 void VncView::updateImage(int x, int y, int w, int h)
272 {
273         if(!QApplication::focusWidget()) { //no focus, we're probably minimized
274                 return;
275         }
276
277      //kDebug(5011) << "got update" << width() << height();
278
279      /*
280      static unsigned int frames = 0;
281      static unsigned int updates = 0;
282      static QTime time = QTime::currentTime();
283      updates++;
284      if(updates % 100 == 0)
285              kDebug(5011) << "u/s: " << updates/double(time.elapsed()) * 1000.0;
286 if(x == 0 and y == 0) {
287         frames++;
288      if(frames % 100 == 0)
289              kDebug(5011) << "f/s: " << frames/double(time.elapsed()) * 1000.0;
290 }
291 */
292
293     m_x = x;
294     m_y = y;
295     m_w = w;
296     m_h = h;
297
298
299         //with scaled view, artefacts occur because of rounding errors
300         //we'll try to only update chunks of screen space that correspond to integer pixel sizes in m_frame
301         //put this into paintEvent
302         /*
303         int frame_x = x/m_horizontalFactor;
304         int frame_w = w/m_horizontalFactor;
305         int frame_y = y/m_verticalFactor;
306         int frame_h = h/m_verticalFactor;
307         
308         m_x = frame_x*m_horizontalFactor;
309         m_y = frame_y*m_verticalFactor;
310
311         m_w = (frame_w+2)*m_horizontalFactor;
312         m_h = (frame_h+2)*m_verticalFactor;
313         kDebug(5011)<< "update);
314         */
315
316
317     if (m_horizontalFactor != 1.0 || m_verticalFactor != 1.0) {
318         // If the view is scaled, grow the update rectangle to avoid artifacts
319         int x_extrapixels = 1.0/m_horizontalFactor + 1;
320         int y_extrapixels = 1.0/m_verticalFactor + 1;
321
322         m_x-=x_extrapixels;
323         m_y-=y_extrapixels;
324         m_w+=2*x_extrapixels;
325         m_h+=2*y_extrapixels;
326     }
327
328     m_frame = vncThread.image();
329
330     if (!m_initDone) {
331         setAttribute(Qt::WA_StaticContents);
332         setAttribute(Qt::WA_OpaquePaintEvent);
333         installEventFilter(this);
334
335         setCursor(((m_dotCursorState == CursorOn) || m_forceLocalCursor) ? localDotCursor() : Qt::BlankCursor);
336
337         setMouseTracking(true); // get mouse events even when there is no mousebutton pressed
338         setFocusPolicy(Qt::WheelFocus);
339         setStatus(Connected);
340 //         emit framebufferSizeChanged(m_frame.width(), m_frame.height());
341         emit connected();
342         
343                 resize(width(), height());
344         
345         m_initDone = true;
346
347     }
348
349         static QSize old_frame_size = QSize();
350     if ((y == 0 && x == 0) && (m_frame.size() != old_frame_size)) {
351             old_frame_size = m_frame.size();
352         kDebug(5011) << "Updating framebuffer size";
353                 setZoomLevel();
354         emit framebufferSizeChanged(m_frame.width(), m_frame.height());
355     }
356
357     m_repaint = true;
358     repaint(qRound(m_x * m_horizontalFactor), qRound(m_y * m_verticalFactor), qRound(m_w * m_horizontalFactor), qRound(m_h * m_verticalFactor));
359     m_repaint = false;
360 }
361
362 void VncView::setViewOnly(bool viewOnly)
363 {
364     RemoteView::setViewOnly(viewOnly);
365
366     m_dontSendClipboard = viewOnly;
367
368     if (viewOnly)
369         setCursor(Qt::ArrowCursor);
370     else
371         setCursor(m_dotCursorState == CursorOn ? localDotCursor() : Qt::BlankCursor);
372 }
373
374 void VncView::showDotCursor(DotCursorState state)
375 {
376     RemoteView::showDotCursor(state);
377
378     setCursor(state == CursorOn ? localDotCursor() : Qt::BlankCursor);
379 }
380
381 void VncView::enableScaling(bool scale)
382 {
383     RemoteView::enableScaling(scale);
384 }
385
386 //level should be in [0, 100]
387 void VncView::setZoomLevel(int level)
388 {
389         Q_ASSERT(parentWidget() != 0);
390
391         if(level == -1) { //handle resize
392                 resize(m_frame.width()*m_horizontalFactor, m_frame.height()*m_verticalFactor);
393                 return;
394         }
395
396         double factor; //actual magnification
397         if(level > 95) {
398                 factor = 2.0;
399         } else if(level > 90) {
400                 factor = 1.0;
401         } else {
402                 const double min_horiz_factor = double(parentWidget()->width())/m_frame.width();
403                 const double min_vert_factor = double(parentWidget()->height())/m_frame.height();
404                 const double fit_screen_factor = qMin(min_horiz_factor, min_vert_factor);
405
406                 //level=900 => factor=1.0, level=0 => factor=fit_screen_factor
407                 factor = (level)/90.0*(1.0 - fit_screen_factor) + fit_screen_factor;
408         }
409
410         if(factor < 0) {
411                 //remote display smaller than local?
412                 kDebug(5011) << "remote display smaller than local?";
413                 factor = 1.0;
414         }
415         if(factor != factor) //nan
416                 factor = 1.0;
417         
418         kDebug(5011) << "factor" << factor;
419
420         m_verticalFactor = m_horizontalFactor = factor;
421         resize(m_frame.width()*factor, m_frame.height()*factor);
422 }
423
424 void VncView::setCut(const QString &text)
425 {
426     m_dontSendClipboard = true;
427     m_clipboard->setText(text, QClipboard::Clipboard);
428     m_clipboard->setText(text, QClipboard::Selection);
429     m_dontSendClipboard = false;
430 }
431
432 void VncView::paintEvent(QPaintEvent *event)
433 {
434      //kDebug(5011) << "paint event: x: " << m_x << ", y: " << m_y << ", w: " << m_w << ", h: " << m_h;
435     if (m_frame.isNull() || m_frame.format() == QImage::Format_Invalid) {
436         kDebug(5011) << "no valid image to paint";
437         RemoteView::paintEvent(event);
438         return;
439     }
440
441     event->accept();
442
443     QPainter painter(this);
444
445         Qt::TransformationMode transformation_mode = Qt::SmoothTransformation;
446         if( m_horizontalFactor >= 1.0 )
447                 transformation_mode = Qt::FastTransformation;
448
449     if (m_repaint and !force_full_repaint) {
450 //         kDebug(5011) << "normal repaint";
451         painter.drawImage(QRect(qRound(m_x*m_horizontalFactor), qRound(m_y*m_verticalFactor),
452                                 qRound(m_w*m_horizontalFactor), qRound(m_h*m_verticalFactor)), 
453                           m_frame.copy(m_x, m_y, m_w, m_h).scaled(qRound(m_w*m_horizontalFactor), 
454                                                                   qRound(m_h*m_verticalFactor),
455                                                                   Qt::IgnoreAspectRatio, transformation_mode));
456     } else {
457          //kDebug(5011) << "resize repaint";
458         QRect rect = event->rect();
459         if (!force_full_repaint and (rect.width() != width() || rect.height() != height())) {
460           //   kDebug(5011) << "Partial repaint";
461             const int sx = rect.x()/m_horizontalFactor;
462             const int sy = rect.y()/m_verticalFactor;
463             const int sw = rect.width()/m_horizontalFactor;
464             const int sh = rect.height()/m_verticalFactor;
465             painter.drawImage(rect, 
466                               m_frame.copy(sx, sy, sw, sh).scaled(rect.width(), rect.height(),
467                                                                   Qt::IgnoreAspectRatio, transformation_mode));
468         } else {
469              kDebug(5011) << "Full repaint" << width() << height() << m_frame.width() << m_frame.height();
470             painter.drawImage(QRect(0, 0, width(), height()), 
471                               m_frame.scaled(m_frame.width() * m_horizontalFactor, m_frame.height() * m_verticalFactor,
472                                              Qt::IgnoreAspectRatio, transformation_mode));
473                         force_full_repaint = false;
474         }
475     }
476
477         //draw local cursor ourselves, normal mouse pointer doesn't deal with scrolling
478         if((m_dotCursorState == CursorOn) || m_forceLocalCursor) {
479 #if QT_VERSION >= 0x040500
480                 painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
481 #endif
482                 //rectangle size includes 1px pen width
483                 painter.drawRect(cursor_x*m_horizontalFactor - cursor_size/2, cursor_y*m_verticalFactor - cursor_size/2, cursor_size-1, cursor_size-1);
484         }
485
486     RemoteView::paintEvent(event);
487 }
488
489 void VncView::resizeEvent(QResizeEvent *event)
490 {
491     RemoteView::resizeEvent(event);
492 }
493
494 bool VncView::event(QEvent *event)
495 {
496     switch (event->type()) {
497     case QEvent::KeyPress:
498     case QEvent::KeyRelease:
499 //         kDebug(5011) << "keyEvent";
500         keyEventHandler(static_cast<QKeyEvent*>(event));
501         return true;
502         break;
503     case QEvent::MouseButtonDblClick:
504     case QEvent::MouseButtonPress:
505     case QEvent::MouseButtonRelease:
506     case QEvent::MouseMove:
507 //         kDebug(5011) << "mouseEvent";
508         mouseEventHandler(static_cast<QMouseEvent*>(event));
509         return true;
510         break;
511     case QEvent::Wheel:
512 //         kDebug(5011) << "wheelEvent";
513         wheelEventHandler(static_cast<QWheelEvent*>(event));
514         return true;
515         break;
516     case QEvent::WindowActivate: //input panel may have been closed, prevent IM from interfering with hardware keyboard
517         setAttribute(Qt::WA_InputMethodEnabled, false);
518         //fall through
519     default:
520         return RemoteView::event(event);
521     }
522 }
523
524 //call with e == 0 to flush held events
525 void VncView::mouseEventHandler(QMouseEvent *e)
526 {
527         static bool tap_detected = false;
528         static bool double_tap_detected = false;
529         static bool tap_drag_detected = false;
530         static QTime press_time;
531         static QTime up_time; //used for double clicks/tap&drag, for time after first tap
532
533         const int TAP_PRESS_TIME = 180;
534         const int DOUBLE_TAP_UP_TIME = 500;
535
536         if(!e) { //flush held taps
537                 if(tap_detected) {
538                         m_buttonMask |= 0x01;
539                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
540                         m_buttonMask &= 0xfe;
541                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
542                         tap_detected = false;
543                 } else if(double_tap_detected and press_time.elapsed() > TAP_PRESS_TIME) { //got tap + another press -> tap & drag
544                         m_buttonMask |= 0x01;
545                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
546                         double_tap_detected = false;
547                         tap_drag_detected = true;
548                 }
549                         
550                 return;
551         }
552
553         if(e->x() < 0 or e->y() < 0) { //QScrollArea tends to send invalid events sometimes...
554                 e->ignore();
555                 return;
556         }
557
558         cursor_x = qRound(e->x()/m_horizontalFactor);
559         cursor_y = qRound(e->y()/m_verticalFactor);
560         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask); // plain move event
561
562         if(!disable_tapping and e->button() == Qt::LeftButton) { //implement touchpad-like input for left button
563                 if(e->type() == QEvent::MouseButtonPress or e->type() == QEvent::MouseButtonDblClick) {
564                         press_time.start();
565                         if(tap_detected and up_time.elapsed() < DOUBLE_TAP_UP_TIME) {
566                                 tap_detected = false;
567                                 double_tap_detected = true;
568
569                                 QTimer::singleShot(TAP_PRESS_TIME, this, SLOT(mouseEventHandler()));
570                         }
571                 } else if(e->type() == QEvent::MouseButtonRelease) {
572                         if(tap_drag_detected) {
573                                 m_buttonMask &= 0xfe;
574                                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
575                                 tap_drag_detected = false;
576                         } else if(double_tap_detected) { //double click
577                                 double_tap_detected = false;
578
579                                 m_buttonMask |= 0x01;
580                                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
581                                 m_buttonMask &= 0xfe;
582                                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
583                                 m_buttonMask |= 0x01;
584                                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
585                                 m_buttonMask &= 0xfe;
586                                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
587                         } else if(press_time.elapsed() < TAP_PRESS_TIME) { //tap
588                                 up_time.start();
589                                 tap_detected = true;
590                                 QTimer::singleShot(DOUBLE_TAP_UP_TIME, this, SLOT(mouseEventHandler()));
591                         }
592
593                 }
594         } else { //middle or right button, send directly
595                 if ((e->type() == QEvent::MouseButtonPress)) {
596                     if (e->button() & Qt::MidButton)
597                         m_buttonMask |= 0x02;
598                     if (e->button() & Qt::RightButton)
599                         m_buttonMask |= 0x04;
600                 } else if (e->type() == QEvent::MouseButtonRelease) {
601                     if (e->button() & Qt::MidButton)
602                         m_buttonMask &= 0xfd;
603                     if (e->button() & Qt::RightButton)
604                         m_buttonMask &= 0xfb;
605                 }
606                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
607         }
608
609         //prevent local cursor artifacts
610         static int old_cursor_x = cursor_x;
611         static int old_cursor_y = cursor_y;
612         if(((m_dotCursorState == CursorOn) || m_forceLocalCursor)
613         and (cursor_x != old_cursor_x or cursor_y != old_cursor_y)) {
614                 //clear last position
615                 repaint(old_cursor_x*m_horizontalFactor - cursor_size/2, old_cursor_y*m_verticalFactor - cursor_size/2, cursor_size, cursor_size);
616                 //and refresh new one
617                 repaint(cursor_x*m_horizontalFactor - cursor_size/2, cursor_y*m_verticalFactor - cursor_size/2, cursor_size, cursor_size);
618
619                 old_cursor_x = cursor_x; old_cursor_y = cursor_y;
620         }
621 }
622
623 void VncView::wheelEventHandler(QWheelEvent *event)
624 {
625     int eb = 0;
626     if (event->delta() < 0)
627         eb |= 0x10;
628     else
629         eb |= 0x8;
630
631     const int x = qRound(event->x() / m_horizontalFactor);
632     const int y = qRound(event->y() / m_verticalFactor);
633
634     vncThread.mouseEvent(x, y, eb | m_buttonMask);
635     vncThread.mouseEvent(x, y, m_buttonMask);
636 }
637
638 void VncView::keyEventHandler(QKeyEvent *e)
639 {
640     // strip away autorepeating KeyRelease; see bug #206598
641     if (e->isAutoRepeat() && (e->type() == QEvent::KeyRelease)) {
642         return;
643     }
644
645 // parts of this code are based on http://italc.sourcearchive.com/documentation/1.0.9.1/vncview_8cpp-source.html
646     rfbKeySym k = e->nativeVirtualKey();
647
648     // we do not handle Key_Backtab separately as the Shift-modifier
649     // is already enabled
650     if (e->key() == Qt::Key_Backtab) {
651         k = XK_Tab;
652     }
653
654     const bool pressed = (e->type() == QEvent::KeyPress);
655
656 #ifdef Q_WS_MAEMO_5
657     //don't send ISO_Level3_Shift (would break things like Win+0-9)
658     //also enable IM so symbol key works
659     if(k == 0xfe03) {
660             setAttribute(Qt::WA_InputMethodEnabled, pressed);
661             e->ignore();
662             return;
663     }
664 #endif
665
666     // handle modifiers
667     if (k == XK_Shift_L || k == XK_Control_L || k == XK_Meta_L || k == XK_Alt_L) {
668         if (pressed) {
669             m_mods[k] = true;
670         } else if (m_mods.contains(k)) {
671             m_mods.remove(k);
672         } else {
673             unpressModifiers();
674         }
675     }
676
677
678         int current_zoom = -1;
679         if(e->key() == Qt::Key_F8)
680                 current_zoom = left_zoom;
681         else if(e->key() == Qt::Key_F7)
682                 current_zoom = right_zoom;
683         else if (k) {
684         //      kDebug(5011) << "got '" << e->text() << "'.";
685                 vncThread.keyEvent(k, pressed);
686         } else {
687                 kDebug(5011) << "nativeVirtualKey() for '" << e->text() << "' failed.";
688                 return;
689         }       
690         
691         if(current_zoom == -1)
692                 return;
693
694         //handle zoom buttons
695         if(current_zoom == 0) { //left click
696                 if(pressed)
697                         m_buttonMask |= 0x01;
698                 else
699                         m_buttonMask &= 0xfe;
700                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
701         } else if(current_zoom == 1) { //right click
702                 if(pressed)
703                         m_buttonMask |= 0x04;
704                 else
705                         m_buttonMask &= 0xfb;
706                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
707         } else if(current_zoom == 2) { //middle click
708                 if(pressed)
709                         m_buttonMask |= 0x02;
710                 else
711                         m_buttonMask &= 0xfd;
712                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
713         } else if(current_zoom == 3 and pressed) { //wheel up
714                 int eb = 0x8;
715                 vncThread.mouseEvent(cursor_x, cursor_y, eb | m_buttonMask);
716                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
717         } else if(current_zoom == 4 and pressed) { //wheel down
718                 int eb = 0x10;
719                 vncThread.mouseEvent(cursor_x, cursor_y, eb | m_buttonMask);
720                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
721         } else if(current_zoom == 5) { //page up
722                 vncThread.keyEvent(0xff55, pressed);
723         } else if(current_zoom == 6) { //page down
724                 vncThread.keyEvent(0xff56, pressed);
725         }
726 }
727
728 void VncView::unpressModifiers()
729 {
730     const QList<unsigned int> keys = m_mods.keys();
731     QList<unsigned int>::const_iterator it = keys.constBegin();
732     while (it != keys.end()) {
733         vncThread.keyEvent(*it, false);
734         it++;
735     }
736     m_mods.clear();
737 }
738
739 void VncView::clipboardSelectionChanged()
740 {
741     if (m_status != Connected)
742         return;
743
744     if (m_clipboard->ownsSelection() || m_dontSendClipboard)
745         return;
746
747     const QString text = m_clipboard->text(QClipboard::Selection);
748
749     vncThread.clientCut(text);
750 }
751
752 void VncView::clipboardDataChanged()
753 {
754     if (m_status != Connected)
755         return;
756
757     if (m_clipboard->ownsClipboard() || m_dontSendClipboard)
758         return;
759
760     const QString text = m_clipboard->text(QClipboard::Clipboard);
761
762     vncThread.clientCut(text);
763 }
764
765 //fake key events
766 void VncView::sendKey(Qt::Key key)
767 {
768         //convert Qt::Key into x11 keysym
769         int k = 0;
770         switch(key) {
771         case Qt::Key_Escape:
772                 k = 0xff1b;
773                 break;
774         case Qt::Key_Tab:
775                 k = 0xff09;
776                 break;
777         case Qt::Key_PageUp:
778                 k = 0xff55;
779                 break;
780         case Qt::Key_PageDown:
781                 k = 0xff56;
782                 break;
783         case Qt::Key_Return:
784                 k = 0xff0d;
785                 break;
786         case Qt::Key_Insert:
787                 k = 0xff63;
788                 break;
789         case Qt::Key_Delete:
790                 k = 0xffff;
791                 break;
792         case Qt::Key_Home:
793                 k = 0xff50;
794                 break;
795         case Qt::Key_End:
796                 k = 0xff57;
797                 break;
798         case Qt::Key_Backspace:
799                 k = 0xff08;
800                 break;
801         case Qt::Key_F1:
802         case Qt::Key_F2:
803         case Qt::Key_F3:
804         case Qt::Key_F4:
805         case Qt::Key_F5:
806         case Qt::Key_F6:
807         case Qt::Key_F7:
808         case Qt::Key_F8:
809         case Qt::Key_F9:
810         case Qt::Key_F10:
811         case Qt::Key_F11:
812         case Qt::Key_F12:
813                 k = 0xffbe + int(key - Qt::Key_F1);
814                 break;
815         case Qt::Key_Pause:
816                 k = 0xff13;
817                 break;
818         case Qt::Key_Print:
819                 k = 0xff61;
820                 break;
821         case Qt::Key_Menu:
822                 k = 0xff67;
823                 break;
824         case Qt::Key_Meta:
825         case Qt::MetaModifier:
826                 k = XK_Super_L;
827                 break;
828         case Qt::Key_Alt:
829         case Qt::AltModifier:
830                 k = XK_Alt_L;
831                 break;
832         case Qt::Key_Control:
833         case Qt::ControlModifier:
834                 k = XK_Control_L;
835                 break;
836         default:
837                 kDebug(5011) << "sendKey(): Unhandled Qt::Key value " << key;
838                 return;
839         }
840
841         if (k == XK_Shift_L || k == XK_Control_L || k == XK_Meta_L || k == XK_Alt_L || k == XK_Super_L) {
842                 if (m_mods.contains(k)) { //release
843                         m_mods.remove(k);
844                         vncThread.keyEvent(k, false);
845                 } else { //press
846                         m_mods[k] = true;
847                         vncThread.keyEvent(k, true);
848                 }
849         } else { //normal key
850                 vncThread.keyEvent(k, true);
851                 vncThread.keyEvent(k, false);
852         }
853 }
854
855 void VncView::sendKeySequence(QKeySequence keys)
856 {
857         Q_ASSERT(keys.count() <= 1); //we can only handle a single combination
858
859         //to get at individual key presses, we split 'keys' into its components
860         QList<int> key_list;
861         for(int i = 0; ; i++) {
862                 QString k = keys.toString().section('+', i, i);
863                 if(k.isEmpty())
864                         break;
865                 //kDebug(5011) << "found key: " << k;
866                 if(k == "Alt") {
867                         key_list.append(Qt::Key_Alt);
868                 } else if(k == "Ctrl") {
869                         key_list.append(Qt::Key_Control);
870                 } else if(k == "Meta") {
871                         key_list.append(Qt::Key_Meta);
872                 } else {
873                         key_list.append(QKeySequence(k)[0]);
874                 }
875         }
876         
877         for(int i = 0; i < key_list.count(); i++)
878                 sendKey(Qt::Key(key_list.at(i)));
879
880         //release modifiers (everything before final key)
881         for(int i = key_list.count()-2; i >= 0; i--)
882                 sendKey(Qt::Key(key_list.at(i)));
883 }
884
885 void VncView::reloadSettings()
886 {
887         QSettings settings;
888         left_zoom = settings.value("left_zoom", 0).toInt();
889         right_zoom = settings.value("right_zoom", 1).toInt();
890         disable_tapping = settings.value("disable_tapping", false).toBool();
891
892         bool always_show_local_cursor = settings.value("always_show_local_cursor", false).toBool();
893         if(always_show_local_cursor)
894                 showDotCursor(CursorOn);
895
896         enableScaling(true);
897 }
898
899 //convert commitString into keyevents
900 void VncView::inputMethodEvent(QInputMethodEvent *event)
901 {
902         //TODO handle replacements
903         //NOTE for the return key to work Qt needs to enable multiline input, which only works for Q(Plain)TextEdit
904
905         //kDebug(5011) << event->commitString() << "|" << event->preeditString() << "|" << event->replacementLength() << "|" << event->replacementStart();
906         QString letters = event->commitString();
907         for(int i = 0; i < letters.length(); i++) {
908                 char k = letters.at(i).toLatin1(); //works with all 'normal' keys, not umlauts.
909                 if(!k) {
910                         kDebug(5011) << "unhandled key";
911                         continue;
912                 }
913                 vncThread.keyEvent(k, true);
914                 vncThread.keyEvent(k, false);
915         }
916 }
917
918
919 #include "moc_vncview.cpp"