add key menu
[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      static unsigned int frames = 0;
293      static unsigned int updates = 0;
294      static QTime time = QTime::currentTime();
295      updates++;
296      if(updates % 100 == 0)
297              kDebug(5011) << "u/s: " << updates/double(time.elapsed()) * 1000.0;
298 if(x == 0 and y == 0) {
299         frames++;
300      if(frames % 100 == 0)
301              kDebug(5011) << "f/s: " << frames/double(time.elapsed()) * 1000.0;
302 }
303
304     m_x = x;
305     m_y = y;
306     m_w = w;
307     m_h = h;
308
309     if (m_horizontalFactor != 1.0 || m_verticalFactor != 1.0) {
310         // If the view is scaled, grow the update rectangle to avoid artifacts
311         int x_extrapixels = 1.0/m_horizontalFactor+1;
312         int y_extrapixels = 1.0/m_verticalFactor+1;
313
314         m_x-=x_extrapixels;
315         m_y-=y_extrapixels;
316         m_w+=2*x_extrapixels;
317         m_h+=2*y_extrapixels;
318     }
319
320     m_frame = vncThread.image();
321
322     if (!m_initDone) {
323         setAttribute(Qt::WA_StaticContents);
324         setAttribute(Qt::WA_OpaquePaintEvent);
325         installEventFilter(this);
326
327         setCursor(((m_dotCursorState == CursorOn) || m_forceLocalCursor) ? localDotCursor() : Qt::BlankCursor);
328
329         setMouseTracking(true); // get mouse events even when there is no mousebutton pressed
330         setFocusPolicy(Qt::WheelFocus);
331         setStatus(Connected);
332 //         emit framebufferSizeChanged(m_frame.width(), m_frame.height());
333         emit connected();
334         
335         if (m_scale) {
336             if (parentWidget())
337                 scaleResize(parentWidget()->width(), parentWidget()->height());
338             else
339                 scaleResize(width(), height());
340         } 
341         
342         m_initDone = true;
343
344     }
345
346         static QSize old_frame_size = QSize();
347     if ((y == 0 && x == 0) && (m_frame.size() != old_frame_size)) {
348             old_frame_size = m_frame.size();
349         kDebug(5011) << "Updating framebuffer size";
350         if (m_scale) {
351             //setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
352             if (parentWidget())
353                 scaleResize(parentWidget()->width(), parentWidget()->height());
354         } else {
355             kDebug(5011) << "Resizing: " << m_frame.width() << m_frame.height();
356             resize(m_frame.width(), m_frame.height());
357             //setMaximumSize(m_frame.width(), m_frame.height()); //This is a hack to force Qt to center the view in the scroll area
358             //setMinimumSize(m_frame.width(), m_frame.height());
359         }
360         emit framebufferSizeChanged(m_frame.width(), m_frame.height());
361     }
362
363     m_repaint = true;
364     repaint(qRound(m_x * m_horizontalFactor), qRound(m_y * m_verticalFactor), qRound(m_w * m_horizontalFactor), qRound(m_h * m_verticalFactor));
365     m_repaint = false;
366 }
367
368 void VncView::setViewOnly(bool viewOnly)
369 {
370     RemoteView::setViewOnly(viewOnly);
371
372     m_dontSendClipboard = viewOnly;
373
374     if (viewOnly)
375         setCursor(Qt::ArrowCursor);
376     else
377         setCursor(m_dotCursorState == CursorOn ? localDotCursor() : Qt::BlankCursor);
378 }
379
380 void VncView::showDotCursor(DotCursorState state)
381 {
382     RemoteView::showDotCursor(state);
383
384     setCursor(state == CursorOn ? localDotCursor() : Qt::BlankCursor);
385 }
386
387 void VncView::enableScaling(bool scale)
388 {
389     RemoteView::enableScaling(scale);
390
391     if (scale) {
392         //setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
393         //setMinimumSize(1, 1);
394         if (parentWidget())
395             scaleResize(parentWidget()->width(), parentWidget()->height());
396             else
397                 scaleResize(width(), height());
398     } else {
399         m_verticalFactor = 1.0;
400         m_horizontalFactor = 1.0;
401
402         //setMaximumSize(m_frame.width(), m_frame.height()); //This is a hack to force Qt to center the view in the scroll area
403         //setMinimumSize(m_frame.width(), m_frame.height());
404         resize(m_frame.width(), m_frame.height());
405     }
406 }
407
408 void VncView::setCut(const QString &text)
409 {
410     m_dontSendClipboard = true;
411     m_clipboard->setText(text, QClipboard::Clipboard);
412     m_clipboard->setText(text, QClipboard::Selection);
413     m_dontSendClipboard = false;
414 }
415
416 void VncView::paintEvent(QPaintEvent *event)
417 {
418      //kDebug(5011) << "paint event: x: " << m_x << ", y: " << m_y << ", w: " << m_w << ", h: " << m_h;
419     if (m_frame.isNull() || m_frame.format() == QImage::Format_Invalid) {
420         kDebug(5011) << "no valid image to paint";
421         RemoteView::paintEvent(event);
422         return;
423     }
424
425     event->accept();
426
427     QPainter painter(this);
428
429     if (m_repaint and !force_full_repaint) {
430 //         kDebug(5011) << "normal repaint";
431         painter.drawImage(QRect(qRound(m_x*m_horizontalFactor), qRound(m_y*m_verticalFactor),
432                                 qRound(m_w*m_horizontalFactor), qRound(m_h*m_verticalFactor)), 
433                           m_frame.copy(m_x, m_y, m_w, m_h).scaled(qRound(m_w*m_horizontalFactor), 
434                                                                   qRound(m_h*m_verticalFactor),
435                                                                   Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
436     } else {
437          //kDebug(5011) << "resize repaint";
438         QRect rect = event->rect();
439         if (!force_full_repaint and (rect.width() != width() || rect.height() != height())) {
440           //   kDebug(5011) << "Partial repaint";
441             const int sx = rect.x()/m_horizontalFactor;
442             const int sy = rect.y()/m_verticalFactor;
443             const int sw = rect.width()/m_horizontalFactor;
444             const int sh = rect.height()/m_verticalFactor;
445             painter.drawImage(rect, 
446                               m_frame.copy(sx, sy, sw, sh).scaled(rect.width(), rect.height(),
447                                                                   Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
448         } else {
449              kDebug(5011) << "Full repaint" << width() << height() << m_frame.width() << m_frame.height();
450             painter.drawImage(QRect(0, 0, width(), height()), 
451                               m_frame.scaled(m_frame.width() * m_horizontalFactor, m_frame.height() * m_verticalFactor,
452                                              Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
453             force_full_repaint = false;
454         }
455     }
456
457     RemoteView::paintEvent(event);
458 }
459
460 void VncView::resizeEvent(QResizeEvent *event)
461 {
462     RemoteView::resizeEvent(event);
463     scaleResize(event->size().width(), event->size().height());
464     forceFullRepaint();
465 }
466
467 bool VncView::event(QEvent *event)
468 {
469     switch (event->type()) {
470     case QEvent::KeyPress:
471     case QEvent::KeyRelease:
472 //         kDebug(5011) << "keyEvent";
473         keyEventHandler(static_cast<QKeyEvent*>(event));
474         return true;
475         break;
476     case QEvent::MouseButtonDblClick:
477     case QEvent::MouseButtonPress:
478     case QEvent::MouseButtonRelease:
479     case QEvent::MouseMove:
480 //         kDebug(5011) << "mouseEvent";
481         mouseEventHandler(static_cast<QMouseEvent*>(event));
482         return true;
483         break;
484     case QEvent::Wheel:
485 //         kDebug(5011) << "wheelEvent";
486         wheelEventHandler(static_cast<QWheelEvent*>(event));
487         return true;
488         break;
489     case QEvent::WindowActivate: //input panel may have been closed, prevent IM from interfering with hardware keyboard
490         setAttribute(Qt::WA_InputMethodEnabled, false);
491         //fall through
492     default:
493         return RemoteView::event(event);
494     }
495 }
496
497 //call with e == 0 to flush held events
498 void VncView::mouseEventHandler(QMouseEvent *e)
499 {
500         static bool tap_detected = false;
501         static bool double_tap_detected = false;
502         static bool tap_drag_detected = false;
503         static QTime press_time;
504         static QTime up_time; //used for double clicks/tap&drag, for time after first tap
505
506         const int TAP_PRESS_TIME = 180;
507         const int DOUBLE_TAP_UP_TIME = 500;
508
509         if(!e) { //flush held taps
510                 if(tap_detected) {
511                         m_buttonMask |= 0x01;
512                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
513                         m_buttonMask &= 0xfe;
514                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
515                         tap_detected = false;
516                 } else if(double_tap_detected and press_time.elapsed() > TAP_PRESS_TIME) { //got tap + another press -> tap & drag
517                         m_buttonMask |= 0x01;
518                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
519                         double_tap_detected = false;
520                         tap_drag_detected = true;
521                 }
522                         
523                 return;
524         }
525
526         if(e->x() < 0 or e->y() < 0) { //QScrollArea tends to send invalid events sometimes...
527                 e->ignore();
528                 return;
529         }
530
531         cursor_x = qRound(e->x()/m_horizontalFactor);
532         cursor_y = qRound(e->y()/m_verticalFactor);
533         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask); // plain move event
534
535         if(disable_tapping) { //only move cursor
536                 e->ignore();
537                 return;
538         }
539
540         if(e->type() == QEvent::MouseButtonPress or e->type() == QEvent::MouseButtonDblClick) {
541                 press_time.start();
542                 if(tap_detected and up_time.elapsed() < DOUBLE_TAP_UP_TIME) {
543                         tap_detected = false;
544                         double_tap_detected = true;
545
546                         QTimer::singleShot(TAP_PRESS_TIME, this, SLOT(mouseEventHandler()));
547                 }
548         } else if(e->type() == QEvent::MouseButtonRelease) {
549                 if(tap_drag_detected) {
550                         m_buttonMask &= 0xfe;
551                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
552                         tap_drag_detected = false;
553                 } else if(double_tap_detected) { //double click
554                         double_tap_detected = false;
555
556                         m_buttonMask |= 0x01;
557                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
558                         m_buttonMask &= 0xfe;
559                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
560                         m_buttonMask |= 0x01;
561                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
562                         m_buttonMask &= 0xfe;
563                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
564                 } else if(press_time.elapsed() < TAP_PRESS_TIME) { //tap
565                         up_time.start();
566                         tap_detected = true;
567                         QTimer::singleShot(DOUBLE_TAP_UP_TIME, this, SLOT(mouseEventHandler()));
568                 }
569
570         }
571
572 /* for reference:
573     if (e->type() != QEvent::MouseMove) {
574         if ((e->type() == QEvent::MouseButtonPress)) {
575             if (e->button() & Qt::LeftButton)
576                 m_buttonMask |= 0x01;
577             if (e->button() & Qt::MidButton)
578                 m_buttonMask |= 0x02;
579             if (e->button() & Qt::RightButton)
580                 m_buttonMask |= 0x04;
581         } else if (e->type() == QEvent::MouseButtonRelease) {
582             if (e->button() & Qt::LeftButton)
583                 m_buttonMask &= 0xfe;
584             if (e->button() & Qt::MidButton)
585                 m_buttonMask &= 0xfd;
586             if (e->button() & Qt::RightButton)
587                 m_buttonMask &= 0xfb;
588         */
589 }
590
591 void VncView::wheelEventHandler(QWheelEvent *event)
592 {
593     int eb = 0;
594     if (event->delta() < 0)
595         eb |= 0x10;
596     else
597         eb |= 0x8;
598
599     const int x = qRound(event->x() / m_horizontalFactor);
600     const int y = qRound(event->y() / m_verticalFactor);
601
602         kDebug(5011) << "Wheelevent";
603     vncThread.mouseEvent(x, y, eb | m_buttonMask);
604     vncThread.mouseEvent(x, y, m_buttonMask);
605 }
606
607 void VncView::keyEventHandler(QKeyEvent *e)
608 {
609     // strip away autorepeating KeyRelease; see bug #206598
610     if (e->isAutoRepeat() && (e->type() == QEvent::KeyRelease)) {
611         return;
612     }
613
614 // parts of this code are based on http://italc.sourcearchive.com/documentation/1.0.9.1/vncview_8cpp-source.html
615     rfbKeySym k = e->nativeVirtualKey();
616
617     // we do not handle Key_Backtab separately as the Shift-modifier
618     // is already enabled
619     if (e->key() == Qt::Key_Backtab) {
620         k = XK_Tab;
621     }
622
623     const bool pressed = (e->type() == QEvent::KeyPress);
624
625     // handle modifiers
626     if (k == XK_Shift_L || k == XK_Control_L || k == XK_Meta_L || k == XK_Alt_L) {
627         if (pressed) {
628             m_mods[k] = true;
629         } else if (m_mods.contains(k)) {
630             m_mods.remove(k);
631         } else {
632             unpressModifiers();
633         }
634     }
635
636
637         int current_zoom = -1;
638         if(e->key() == Qt::Key_F8)
639                 current_zoom = left_zoom;
640         else if(e->key() == Qt::Key_F7)
641                 current_zoom = right_zoom;
642         else if (k) {
643         //      kDebug(5011) << "got '" << e->text() << "'.";
644                 vncThread.keyEvent(k, pressed);
645         } else {
646                 kDebug(5011) << "nativeVirtualKey() for '" << e->text() << "' failed.";
647                 return;
648         }       
649         
650         if(current_zoom == -1)
651                 return;
652
653         //handle zoom buttons
654         if(current_zoom == 0) { //left click
655                 if(pressed)
656                         m_buttonMask |= 0x01;
657                 else
658                         m_buttonMask &= 0xfe;
659                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
660         } else if(current_zoom == 1) { //right click
661                 if(pressed)
662                         m_buttonMask |= 0x04;
663                 else
664                         m_buttonMask &= 0xfb;
665                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
666         } else if(current_zoom == 2) { //middle click
667                 if(pressed)
668                         m_buttonMask |= 0x02;
669                 else
670                         m_buttonMask &= 0xfd;
671                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
672         } else if(current_zoom == 3 and pressed) { //wheel up
673                 int eb = 0x8;
674                 vncThread.mouseEvent(cursor_x, cursor_y, eb | m_buttonMask);
675                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
676         } else if(current_zoom == 4 and pressed) { //wheel down
677                 int eb = 0x10;
678                 vncThread.mouseEvent(cursor_x, cursor_y, eb | m_buttonMask);
679                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
680         } else if(current_zoom == 5) { //page up
681                 vncThread.keyEvent(0xff55, pressed);
682         } else if(current_zoom == 6) { //page down
683                 vncThread.keyEvent(0xff56, pressed);
684         }
685 }
686
687 void VncView::unpressModifiers()
688 {
689     const QList<unsigned int> keys = m_mods.keys();
690     QList<unsigned int>::const_iterator it = keys.constBegin();
691     while (it != keys.end()) {
692         vncThread.keyEvent(*it, false);
693         it++;
694     }
695     m_mods.clear();
696 }
697
698 void VncView::clipboardSelectionChanged()
699 {
700     kDebug(5011);
701
702     if (m_status != Connected)
703         return;
704
705     if (m_clipboard->ownsSelection() || m_dontSendClipboard)
706         return;
707
708     const QString text = m_clipboard->text(QClipboard::Selection);
709
710     vncThread.clientCut(text);
711 }
712
713 void VncView::clipboardDataChanged()
714 {
715     kDebug(5011);
716
717     if (m_status != Connected)
718         return;
719
720     if (m_clipboard->ownsClipboard() || m_dontSendClipboard)
721         return;
722
723     const QString text = m_clipboard->text(QClipboard::Clipboard);
724
725     vncThread.clientCut(text);
726 }
727
728 //fake key events
729 void VncView::sendKey(Qt::Key key)
730 {
731         //convert Qt::Key into x11 keysym
732         int k = 0;
733         switch(key) {
734         case Qt::Key_Escape:
735                 k = 0xff1b;
736                 break;
737         case Qt::Key_Tab:
738                 k = 0xff09;
739                 break;
740         case Qt::Key_PageUp:
741                 k = 0xff55;
742                 break;
743         case Qt::Key_PageDown:
744                 k = 0xff56;
745                 break;
746         case Qt::Key_Return:
747                 k = 0xff0d;
748                 break;
749         case Qt::Key_Insert:
750                 k = 0xff63;
751                 break;
752         case Qt::Key_Delete:
753                 k = 0xffff;
754                 break;
755         case Qt::Key_Home:
756                 k = 0xff50;
757                 break;
758         case Qt::Key_End:
759                 k = 0xff57;
760                 break;
761         case Qt::Key_Backspace:
762                 k = 0xff08;
763                 break;
764         case Qt::Key_F1:
765         case Qt::Key_F2:
766         case Qt::Key_F3:
767         case Qt::Key_F4:
768         case Qt::Key_F5:
769         case Qt::Key_F6:
770         case Qt::Key_F7:
771         case Qt::Key_F8:
772         case Qt::Key_F9:
773         case Qt::Key_F10:
774         case Qt::Key_F11:
775         case Qt::Key_F12:
776                 k = 0xffbe + int(key - Qt::Key_F1);
777                 break;
778         case Qt::Key_Meta:
779         case Qt::MetaModifier:
780                 k = XK_Super_L;
781                 break;
782         case Qt::Key_Alt:
783         case Qt::AltModifier:
784                 k = XK_Alt_L;
785                 break;
786         case Qt::Key_Control:
787         case Qt::ControlModifier:
788                 k = XK_Control_L;
789                 break;
790         default:
791                 kDebug(5011) << "sendKey(): Unhandled Qt::Key value " << key;
792                 return;
793         }
794
795         if (k == XK_Shift_L || k == XK_Control_L || k == XK_Meta_L || k == XK_Alt_L || k == XK_Super_L) {
796                 if (m_mods.contains(k)) { //release
797                         m_mods.remove(k);
798                         vncThread.keyEvent(k, false);
799                 } else { //press
800                         m_mods[k] = true;
801                         vncThread.keyEvent(k, true);
802                 }
803         } else { //normal key
804                 vncThread.keyEvent(k, true);
805                 vncThread.keyEvent(k, false);
806         }
807 }
808
809 void VncView::sendKeySequence(QKeySequence keys)
810 {
811         Q_ASSERT(keys.count() <= 1); //we can only handle a single combination
812
813         //to get at individual key presses, we split 'keys' into its components
814         QList<int> key_list;
815         for(int i = 0; ; i++) {
816                 QString k = keys.toString().section('+', i, i);
817                 if(k.isEmpty())
818                         break;
819                 kDebug(5011) << "found key: " << k;
820                 if(k == "Alt") {
821                         key_list.append(Qt::Key_Alt);
822                 } else if(k == "Ctrl") {
823                         key_list.append(Qt::Key_Control);
824                 } else if(k == "Meta") {
825                         key_list.append(Qt::Key_Meta);
826                 } else {
827                         key_list.append(QKeySequence(k)[0]);
828                 }
829         }
830         
831         for(int i = 0; i < key_list.count(); i++)
832                 sendKey(Qt::Key(key_list.at(i)));
833
834         //release modifiers (everything before final key)
835         for(int i = key_list.count()-2; i >= 0; i--)
836                 sendKey(Qt::Key(key_list.at(i)));
837 }
838
839 void VncView::reloadSettings()
840 {
841         QSettings settings;
842         left_zoom = settings.value("left_zoom", 0).toInt();
843         right_zoom = settings.value("right_zoom", 1).toInt();
844         disable_tapping = settings.value("disable_tapping", false).toBool();
845 }
846
847 //convert commitString into keyevents
848 void VncView::inputMethodEvent(QInputMethodEvent *event)
849 {
850         //TODO handle replacements
851         //NOTE for the return key to work Qt needs to enable multiline input, which only works for Q(Plain)TextEdit
852
853         //kDebug(5011) << event->commitString() << "|" << event->preeditString() << "|" << event->replacementLength() << "|" << event->replacementStart();
854         QString letters = event->commitString();
855         for(int i = 0; i < letters.length(); i++) {
856                 char k = letters.at(i).toLatin1(); //works with all 'normal' keys, not umlauts.
857                 if(!k) {
858                         kDebug(5011) << "unhandled key";
859                         continue;
860                 }
861                 vncThread.keyEvent(k, true);
862                 vncThread.keyEvent(k, false);
863         }
864 }
865
866
867 #include "moc_vncview.cpp"