diff options
Diffstat (limited to 'krdc/vnc/kvncview.cpp')
-rw-r--r-- | krdc/vnc/kvncview.cpp | 828 |
1 files changed, 828 insertions, 0 deletions
diff --git a/krdc/vnc/kvncview.cpp b/krdc/vnc/kvncview.cpp new file mode 100644 index 00000000..1b6a8de2 --- /dev/null +++ b/krdc/vnc/kvncview.cpp @@ -0,0 +1,828 @@ +/*************************************************************************** + kvncview.cpp - main widget + ------------------- + begin : Thu Dec 20 15:11:42 CET 2001 + copyright : (C) 2001-2003 by Tim Jansen + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "kvncview.h" +#include "vncprefs.h" +#include "vnchostpref.h" + +#include <kapplication.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> +#include <kpassdlg.h> +#include <kdialogbase.h> +#include <kwallet.h> + +#include <qdatastream.h> +#include <dcopclient.h> +#include <qclipboard.h> +#include <qbitmap.h> +#include <qmutex.h> +#include <qvbox.h> +#include <qwaitcondition.h> + +#include "vncviewer.h" + +#include <X11/Xlib.h> + +/* + * appData is our application-specific data which can be set by the user with + * application resource specs. The AppData structure is defined in the header + * file. + */ +AppData appData; +bool appDataConfigured = false; + +Display* dpy; + +static KVncView *kvncview; + +//Passwords and KWallet data +extern KWallet::Wallet *wallet; +bool useKWallet = false; +static QCString password; +static QMutex passwordLock; +static QWaitCondition passwordWaiter; + +const unsigned int MAX_SELECTION_LENGTH = 4096; + + +KVncView::KVncView(QWidget *parent, + const char *name, + const QString &_host, + int _port, + const QString &_password, + Quality quality, + DotCursorState dotCursorState, + const QString &encodings) : + KRemoteView(parent, name, Qt::WResizeNoErase | Qt::WRepaintNoErase | Qt::WStaticContents), + m_cthread(this, m_wthread, m_quitFlag), + m_wthread(this, m_quitFlag), + m_quitFlag(false), + m_enableFramebufferLocking(false), + m_scaling(false), + m_remoteMouseTracking(false), + m_viewOnly(false), + m_buttonMask(0), + m_host(_host), + m_port(_port), + m_dontSendCb(false), + m_cursorState(dotCursorState) +{ + kvncview = this; + password = _password.latin1(); + dpy = qt_xdisplay(); + setFixedSize(16,16); + setFocusPolicy(QWidget::StrongFocus); + + m_cb = QApplication::clipboard(); + connect(m_cb, SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); + connect(m_cb, SIGNAL(dataChanged()), this, SLOT(clipboardChanged())); + + KStandardDirs *dirs = KGlobal::dirs(); + QBitmap cursorBitmap(dirs->findResource("appdata", + "pics/pointcursor.png")); + QBitmap cursorMask(dirs->findResource("appdata", + "pics/pointcursormask.png")); + m_cursor = QCursor(cursorBitmap, cursorMask); + + if ((quality != QUALITY_UNKNOWN) || + !encodings.isNull()) + configureApp(quality, encodings); +} + +void KVncView::showDotCursor(DotCursorState state) { + if (state == m_cursorState) + return; + + m_cursorState = state; + showDotCursorInternal(); +} + +DotCursorState KVncView::dotCursorState() const { + return m_cursorState; +} + +void KVncView::showDotCursorInternal() { + switch (m_cursorState) { + case DOT_CURSOR_ON: + setCursor(m_cursor); + break; + case DOT_CURSOR_OFF: + setCursor(QCursor(Qt::BlankCursor)); + break; + case DOT_CURSOR_AUTO: + if (m_enableClientCursor) + setCursor(QCursor(Qt::BlankCursor)); + else + setCursor(m_cursor); + break; + } +} + +QString KVncView::host() { + return m_host; +} + +int KVncView::port() { + return m_port; +} + +void KVncView::startQuitting() { + m_quitFlag = true; + m_wthread.kick(); + m_cthread.kick(); +} + +bool KVncView::isQuitting() { + return m_quitFlag; +} + +void KVncView::configureApp(Quality q, const QString specialEncodings) { + appDataConfigured = true; + appData.shareDesktop = 1; + appData.viewOnly = 0; + + if (q == QUALITY_LOW) { + appData.useBGR233 = 1; + appData.encodingsString = "background copyrect softcursor tight zlib hextile raw"; + appData.compressLevel = -1; + appData.qualityLevel = 1; + appData.dotCursor = 1; + } + else if (q == QUALITY_MEDIUM) { + appData.useBGR233 = 0; + appData.encodingsString = "background copyrect softcursor tight zlib hextile raw"; + appData.compressLevel = -1; + appData.qualityLevel = 7; + appData.dotCursor = 1; + } + else if ((q == QUALITY_HIGH) || (q == QUALITY_UNKNOWN)) { + appData.useBGR233 = 0; + appData.encodingsString = "copyrect softcursor hextile raw"; + appData.compressLevel = -1; + appData.qualityLevel = 9; + appData.dotCursor = 1; + } + + if (!specialEncodings.isNull()) + appData.encodingsString = specialEncodings.latin1(); + + appData.nColours = 256; + appData.useSharedColours = 1; + appData.requestedDepth = 0; + + appData.rawDelay = 0; + appData.copyRectDelay = 0; + + if (!appData.dotCursor) + m_cursorState = DOT_CURSOR_OFF; + showDotCursorInternal(); +} + +bool KVncView::checkLocalKRfb() { + if ( m_host != "localhost" && !m_host.isEmpty() ) + return true; + DCOPClient *d = KApplication::dcopClient(); + + int portNum; + QByteArray sdata, rdata; + QCString replyType; + QDataStream arg(sdata, IO_WriteOnly); + arg << QString("krfb"); + if (!d->call ("kded", "kinetd", "port(QString)", sdata, replyType, rdata)) + return true; + + if (replyType != "int") + return true; + + QDataStream answer(rdata, IO_ReadOnly); + answer >> portNum; + + if (m_port != portNum) + return true; + + setStatus(REMOTE_VIEW_DISCONNECTED); + KMessageBox::error(0, + i18n("It is not possible to connect to a local desktop sharing service."), + i18n("Connection Failure")); + emit disconnectedError(); + return false; +} + +bool KVncView::editPreferences( HostPrefPtr host ) +{ + SmartPtr<VncHostPref> hp( host ); + + int ci = hp->quality(); + bool kwallet = hp->useKWallet(); + + // show preferences dialog + KDialogBase *dlg = new KDialogBase( 0L, "dlg", true, + i18n( "VNC Host Preferences for %1" ).arg( host->host() ), + KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true ); + + QVBox *vbox = dlg->makeVBoxMainWidget(); + VncPrefs *prefs = new VncPrefs( vbox ); + QWidget *spacer = new QWidget( vbox ); + vbox->setStretchFactor( spacer, 10 ); + + prefs->setQuality( ci ); + prefs->setShowPrefs(true); + prefs->setUseKWallet(kwallet); + + if ( dlg->exec() == QDialog::Rejected ) + return false; + + ci = prefs->quality(); + hp->setAskOnConnect(prefs->showPrefs()); + hp->setQuality(ci); + hp->setUseKWallet(prefs->useKWallet()); + + delete dlg; + return true; +} + +bool KVncView::start() { + + if (!checkLocalKRfb()) + return false; + + if (!appDataConfigured) { + + HostPreferences *hps = HostPreferences::instance(); + SmartPtr<VncHostPref> hp = + SmartPtr<VncHostPref>(hps->createHostPref(m_host, + VncHostPref::VncType)); + if (hp->askOnConnect()) { + if (!editPreferences(hp)) + return false; + hps->sync(); + } + + int ci = hp->quality(); + + Quality quality; + if (ci == 0) + quality = QUALITY_HIGH; + else if (ci == 1) + quality = QUALITY_MEDIUM; + else if (ci == 2) + quality = QUALITY_LOW; + else { + kdDebug() << "Unknown quality"; + return false; + } + + configureApp(quality); + useKWallet = hp->useKWallet(); + } + + setStatus(REMOTE_VIEW_CONNECTING); + + m_cthread.start(); + setBackgroundMode(Qt::NoBackground); + return true; +} + +KVncView::~KVncView() +{ + startQuitting(); + m_cthread.wait(); + m_wthread.wait(); + freeResources(); +} + +bool KVncView::supportsLocalCursor() const { + return true; +} + +bool KVncView::supportsScaling() const { + return true; +} + +bool KVncView::scaling() const { + return m_scaling; +} + +bool KVncView::viewOnly() { + return m_viewOnly; +} + +QSize KVncView::framebufferSize() { + return m_framebufferSize; +} + +void KVncView::setViewOnly(bool s) { + m_viewOnly = s; + + if (s) + setCursor(Qt::ArrowCursor); + else + showDotCursorInternal(); +} + +void KVncView::enableScaling(bool s) { + bool os = m_scaling; + m_scaling = s; + if (s != os) { + if (s) { + setMaximumSize(m_framebufferSize); + setMinimumSize(m_framebufferSize.width()/16, + m_framebufferSize.height()/16); + } + else + setFixedSize(m_framebufferSize); + } +} + +void KVncView::paintEvent(QPaintEvent *e) { + drawRegion(e->rect().x(), + e->rect().y(), + e->rect().width(), + e->rect().height()); +} + +void KVncView::drawRegion(int x, int y, int w, int h) { + if (m_scaling) + DrawZoomedScreenRegionX11Thread(winId(), width(), height(), + x, y, w, h); + else + DrawScreenRegionX11Thread(winId(), x, y, w, h); +} + +void KVncView::customEvent(QCustomEvent *e) +{ + if (e->type() == ScreenRepaintEventType) { + ScreenRepaintEvent *sre = (ScreenRepaintEvent*) e; + drawRegion(sre->x(), sre->y(),sre->width(), sre->height()); + } + else if (e->type() == ScreenResizeEventType) { + ScreenResizeEvent *sre = (ScreenResizeEvent*) e; + m_framebufferSize = QSize(sre->width(), sre->height()); + setFixedSize(m_framebufferSize); + emit changeSize(sre->width(), sre->height()); + } + else if (e->type() == DesktopInitEventType) { + m_cthread.desktopInit(); + } + else if (e->type() == StatusChangeEventType) { + StatusChangeEvent *sce = (StatusChangeEvent*) e; + setStatus(sce->status()); + if (m_status == REMOTE_VIEW_CONNECTED) { + emit connected(); + setFocus(); + setMouseTracking(true); + } + else if (m_status == REMOTE_VIEW_DISCONNECTED) { + setMouseTracking(false); + emit disconnected(); + } + else if (m_status == REMOTE_VIEW_PREPARING) { + //Login was successfull: Write KWallet password if necessary. + if ( useKWallet && !password.isNull() && wallet && wallet->isOpen() && !wallet->hasEntry(host())) { + wallet->writePassword(host(), password); + } + delete wallet; wallet=0; + } + } + else if (e->type() == PasswordRequiredEventType) { + emit showingPasswordDialog(true); + + if (KPasswordDialog::getPassword(password, i18n("Access to the system requires a password.")) != KPasswordDialog::Accepted) + password = QCString(); + + emit showingPasswordDialog(false); + + passwordLock.lock(); // to guarantee that thread is waiting + passwordWaiter.wakeAll(); + passwordLock.unlock(); + } + else if (e->type() == WalletOpenEventType) { + QString krdc_folder = "KRDC-VNC"; + emit showingPasswordDialog(true); //Bad things happen if you don't do this. + + // Bugfix: Check if wallet has been closed by an outside source + if ( wallet && !wallet->isOpen() ) { + delete wallet; wallet=0; + } + + // Do we need to open the wallet? + if ( !wallet ) { + QString walletName = KWallet::Wallet::NetworkWallet(); + wallet = KWallet::Wallet::openWallet(walletName); + } + + if (wallet && wallet->isOpen()) { + bool walletOK = wallet->hasFolder(krdc_folder); + if (walletOK == false) { + walletOK = wallet->createFolder(krdc_folder); + } + + if (walletOK == true) { + wallet->setFolder(krdc_folder); + QString newPass; + if ( wallet->hasEntry(kvncview->host()) && !wallet->readPassword(kvncview->host(), newPass) ) { + password=newPass.latin1(); + } + } + } + + passwordLock.lock(); // to guarantee that thread is waiting + passwordWaiter.wakeAll(); + passwordLock.unlock(); + + emit showingPasswordDialog(false); + } + else if (e->type() == FatalErrorEventType) { + FatalErrorEvent *fee = (FatalErrorEvent*) e; + setStatus(REMOTE_VIEW_DISCONNECTED); + switch (fee->errorCode()) { + case ERROR_CONNECTION: + KMessageBox::error(0, + i18n("Connection attempt to host failed."), + i18n("Connection Failure")); + break; + case ERROR_PROTOCOL: + KMessageBox::error(0, + i18n("Remote host is using an incompatible protocol."), + i18n("Connection Failure")); + break; + case ERROR_IO: + KMessageBox::error(0, + i18n("The connection to the host has been interrupted."), + i18n("Connection Failure")); + break; + case ERROR_SERVER_BLOCKED: + KMessageBox::error(0, + i18n("Connection failed. The server does not accept new connections."), + i18n("Connection Failure")); + break; + case ERROR_NAME: + KMessageBox::error(0, + i18n("Connection failed. A server with the given name cannot be found."), + i18n("Connection Failure")); + break; + case ERROR_NO_SERVER: + KMessageBox::error(0, + i18n("Connection failed. No server running at the given address and port."), + i18n("Connection Failure")); + break; + case ERROR_AUTHENTICATION: + //Login failed: Remove wallet entry if there is one. + if ( useKWallet && wallet && wallet->isOpen() && wallet->hasEntry(host()) ) { + wallet->removeEntry(host()); + } + KMessageBox::error(0, + i18n("Authentication failed. Connection aborted."), + i18n("Authentication Failure")); + break; + default: + KMessageBox::error(0, + i18n("Unknown error."), + i18n("Unknown Error")); + break; + } + emit disconnectedError(); + } + else if (e->type() == BeepEventType) { + QApplication::beep(); + } + else if (e->type() == ServerCutEventType) { + ServerCutEvent *sce = (ServerCutEvent*) e; + QString ctext = QString::fromUtf8(sce->bytes(), sce->length()); + m_dontSendCb = true; + m_cb->setText(ctext, QClipboard::Clipboard); + m_cb->setText(ctext, QClipboard::Selection); + m_dontSendCb = false; + } + else if (e->type() == MouseStateEventType) { + MouseStateEvent *mse = (MouseStateEvent*) e; + emit mouseStateChanged(mse->x(), mse->y(), mse->buttonMask()); + bool show = m_plom.handlePointerEvent(mse->x(), mse->y()); + if (m_cursorState != DOT_CURSOR_ON) + showDotCursor(show ? DOT_CURSOR_AUTO : DOT_CURSOR_OFF); + } +} + +void KVncView::mouseEvent(QMouseEvent *e) { + if (m_status != REMOTE_VIEW_CONNECTED) + return; + if (m_viewOnly) + return; + + if ( e->type() != QEvent::MouseMove ) { + if ( (e->type() == QEvent::MouseButtonPress) || + (e->type() == QEvent::MouseButtonDblClick)) { + if ( e->button() & LeftButton ) + m_buttonMask |= 0x01; + if ( e->button() & MidButton ) + m_buttonMask |= 0x02; + if ( e->button() & RightButton ) + m_buttonMask |= 0x04; + } + else if ( e->type() == QEvent::MouseButtonRelease ) { + if ( e->button() & LeftButton ) + m_buttonMask &= 0xfe; + if ( e->button() & MidButton ) + m_buttonMask &= 0xfd; + if ( e->button() & RightButton ) + m_buttonMask &= 0xfb; + } + } + + int x = e->x(); + int y = e->y(); + m_plom.registerPointerState(x, y); + if (m_scaling) { + x = (x * m_framebufferSize.width()) / width(); + y = (y * m_framebufferSize.height()) / height(); + } + m_wthread.queueMouseEvent(x, y, m_buttonMask); + + if (m_enableClientCursor) + DrawCursorX11Thread(x, y); // in rfbproto.c +} + +void KVncView::mousePressEvent(QMouseEvent *e) { + mouseEvent(e); + e->accept(); +} + +void KVncView::mouseDoubleClickEvent(QMouseEvent *e) { + mouseEvent(e); + e->accept(); +} + +void KVncView::mouseReleaseEvent(QMouseEvent *e) { + mouseEvent(e); + e->accept(); +} + +void KVncView::mouseMoveEvent(QMouseEvent *e) { + mouseEvent(e); + e->ignore(); +} + +void KVncView::wheelEvent(QWheelEvent *e) { + if (m_status != REMOTE_VIEW_CONNECTED) + return; + if (m_viewOnly) + return; + + int eb = 0; + if ( e->delta() < 0 ) + eb |= 0x10; + else + eb |= 0x8; + + int x = e->pos().x(); + int y = e->pos().y(); + if (m_scaling) { + x = (x * m_framebufferSize.width()) / width(); + y = (y * m_framebufferSize.height()) / height(); + } + m_wthread.queueMouseEvent(x, y, eb|m_buttonMask); + m_wthread.queueMouseEvent(x, y, m_buttonMask); + e->accept(); +} + +void KVncView::pressKey(XEvent *xe) { + KKeyNative k(xe); + uint mod = k.mod(); + if (mod & KKeyNative::modX(KKey::SHIFT)) + m_wthread.queueKeyEvent(XK_Shift_L, true); + if (mod & KKeyNative::modX(KKey::CTRL)) + m_wthread.queueKeyEvent(XK_Control_L, true); + if (mod & KKeyNative::modX(KKey::ALT)) + m_wthread.queueKeyEvent(XK_Alt_L, true); + if (mod & KKeyNative::modX(KKey::WIN)) + m_wthread.queueKeyEvent(XK_Meta_L, true); + + m_wthread.queueKeyEvent(k.sym(), true); + m_wthread.queueKeyEvent(k.sym(), false); + + if (mod & KKeyNative::modX(KKey::WIN)) + m_wthread.queueKeyEvent(XK_Meta_L, false); + if (mod & KKeyNative::modX(KKey::ALT)) + m_wthread.queueKeyEvent(XK_Alt_L, false); + if (mod & KKeyNative::modX(KKey::CTRL)) + m_wthread.queueKeyEvent(XK_Control_L, false); + if (mod & KKeyNative::modX(KKey::SHIFT)) + m_wthread.queueKeyEvent(XK_Shift_L, false); + + m_mods.clear(); +} + +bool KVncView::x11Event(XEvent *e) { + bool pressed; + if (e->type == KeyPress) + pressed = true; + else if (e->type == KeyRelease) + pressed = false; + else + return QWidget::x11Event(e); + + if (!m_viewOnly) { + unsigned int s = KKeyNative(e).sym(); + + switch (s) { + case XK_Meta_L: + case XK_Alt_L: + case XK_Control_L: + case XK_Shift_L: + case XK_Meta_R: + case XK_Alt_R: + case XK_Control_R: + case XK_Shift_R: + if (pressed) + m_mods[s] = true; + else if (m_mods.contains(s)) + m_mods.remove(s); + else + unpressModifiers(); + } + m_wthread.queueKeyEvent(s, pressed); + } + return true; +} + +void KVncView::unpressModifiers() { + QValueList<unsigned int> keys = m_mods.keys(); + QValueList<unsigned int>::const_iterator it = keys.begin(); + while (it != keys.end()) { + m_wthread.queueKeyEvent(*it, false); + it++; + } + m_mods.clear(); +} + +void KVncView::focusOutEvent(QFocusEvent *) { + unpressModifiers(); +} + +QSize KVncView::sizeHint() { + return maximumSize(); +} + +void KVncView::setRemoteMouseTracking(bool s) { + m_remoteMouseTracking = s; +} + +bool KVncView::remoteMouseTracking() { + return m_remoteMouseTracking; +} + +void KVncView::clipboardChanged() { + if (m_status != REMOTE_VIEW_CONNECTED) + return; + + if (m_cb->ownsClipboard() || m_dontSendCb) + return; + + QString text = m_cb->text(QClipboard::Clipboard); + if (text.length() > MAX_SELECTION_LENGTH) + return; + + m_wthread.queueClientCut(text); +} + +void KVncView::selectionChanged() { + if (m_status != REMOTE_VIEW_CONNECTED) + return; + + if (m_cb->ownsSelection() || m_dontSendCb) + return; + + QString text = m_cb->text(QClipboard::Selection); + if (text.length() > MAX_SELECTION_LENGTH) + return; + + m_wthread.queueClientCut(text); +} + + +void KVncView::lockFramebuffer() { + if (m_enableFramebufferLocking) + m_framebufferLock.lock(); +} + +void KVncView::unlockFramebuffer() { + if (m_enableFramebufferLocking) + m_framebufferLock.unlock(); +} + +void KVncView::enableClientCursor(bool enable) { + if (enable) { + m_enableFramebufferLocking = true; // cant be turned off + } + m_enableClientCursor = enable; + lockFramebuffer(); + showDotCursorInternal(); + unlockFramebuffer(); +} + +/*! + \brief Get a password for this host. + Tries to get a password from the url or wallet if at all possible. If + both of these fail, it then asks the user to enter a password. + \note Lots of dialogs can be popped up during this process. The thread + locks and signals are there to protect against deadlocks and other + horribleness. Be careful making changes here. +*/ +int getPassword(char *passwd, int pwlen) { + int retV = 0; + + //Prepare the system + passwordLock.lock(); + + //Try #1: Did the user give a password in the URL? + if (!password.isNull()) { + retV = 1; //got it! + } + + //Try #2: Is there something in the wallet? + if ( !retV && useKWallet ) { + QApplication::postEvent(kvncview, new WalletOpenEvent()); + passwordWaiter.wait(&passwordLock); //block + if (!password.isNull()) retV = 1; //got it! + } + + //Last try: Ask the user + if (!retV) { + QApplication::postEvent(kvncview, new PasswordRequiredEvent()); + passwordWaiter.wait(&passwordLock); //block + if (!password.isNull()) retV = 1; //got it! + } + + //Process the password if we got it, clear it if we didn't + if (retV) { + strncpy(passwd, (const char*)password, pwlen); + } else { + passwd[0] = 0; + } + + //Pack up and go home + passwordLock.unlock(); + if (!retV) kvncview->startQuitting(); + + return retV; +} + +extern int isQuitFlagSet() { + return kvncview->isQuitting() ? 1 : 0; +} + +extern void DrawScreenRegion(int x, int y, int width, int height) { +/* KApplication::kApplication()->lock(); + kvncview->drawRegion(x, y, width, height); + KApplication::kApplication()->unlock(); +*/ + QApplication::postEvent(kvncview, new ScreenRepaintEvent(x, y, width, height)); +} + +// call only from x11 thread! +extern void DrawAnyScreenRegionX11Thread(int x, int y, int width, int height) { + kvncview->drawRegion(x, y, width, height); +} + +extern void EnableClientCursor(int enable) { + kvncview->enableClientCursor(enable); +} + +extern void LockFramebuffer() { + kvncview->lockFramebuffer(); +} + +extern void UnlockFramebuffer() { + kvncview->unlockFramebuffer(); +} + +extern void beep() { + QApplication::postEvent(kvncview, new BeepEvent()); +} + +extern void newServerCut(char *bytes, int length) { + QApplication::postEvent(kvncview, new ServerCutEvent(bytes, length)); +} + +extern void postMouseEvent(int x, int y, int buttonMask) { + QApplication::postEvent(kvncview, new MouseStateEvent(x, y, buttonMask)); +} + +#include "kvncview.moc" |