/*************************************************************************** kvncview.cpp - main widget ------------------- begin : Thu Dec 20 15:11:42 CET 2001 copyright : (C) 2001-2003 by Tim Jansen email : tim@tjansen.de ***************************************************************************/ /*************************************************************************** * * * 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 <tqdatastream.h> #include <dcopclient.h> #include <tqclipboard.h> #include <tqbitmap.h> #include <tqmutex.h> #include <tqvbox.h> #include <tqwaitcondition.h> #include "vncviewer.h" #include <X11/Xlib.h> bool launch_Fullscreen_vnc = false; /* * 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 TQCString password; static TQMutex passwordLock; static TQWaitCondition passwordWaiter; const unsigned int MAX_SELECTION_LENGTH = 4096; KVncView::KVncView(TQWidget *parent, const char *name, const TQString &_host, int _port, const TQString &_password, Quality quality, DotCursorState dotCursorState, const TQString &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(TQWidget::StrongFocus); m_cb = TQApplication::clipboard(); connect(m_cb, TQT_SIGNAL(selectionChanged()), this, TQT_SLOT(selectionChanged())); connect(m_cb, TQT_SIGNAL(dataChanged()), this, TQT_SLOT(clipboardChanged())); KStandardDirs *dirs = KGlobal::dirs(); TQBitmap cursorBitmap(dirs->findResource("appdata", "pics/pointcursor.png")); TQBitmap cursorMask(dirs->findResource("appdata", "pics/pointcursormask.png")); m_cursor = TQCursor(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(TQCursor(Qt::BlankCursor)); break; case DOT_CURSOR_AUTO: if (m_enableClientCursor) setCursor(TQCursor(Qt::BlankCursor)); else setCursor(m_cursor); break; } } TQString 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 TQString 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; TQByteArray sdata, rdata; TQCString replyType; TQDataStream arg(sdata, IO_WriteOnly); arg << TQString("krfb"); if (!d->call ("kded", "kinetd", "port(TQString)", sdata, replyType, rdata)) return true; if (replyType != "int") return true; TQDataStream 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 ); TQVBox *vbox = dlg->makeVBoxMainWidget(); VncPrefs *prefs = new VncPrefs( vbox ); TQWidget *spacer = new TQWidget( vbox ); vbox->setStretchFactor( spacer, 10 ); prefs->setQuality( ci ); prefs->setShowPrefs(true); prefs->setUseKWallet(kwallet); if ( dlg->exec() == TQDialog::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; } bool KVncView::startFullscreen() { return launch_Fullscreen_vnc; } TQSize 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(TQPaintEvent *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(TQCustomEvent *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 = TQSize(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 = TQCString(); emit showingPasswordDialog(false); passwordLock.lock(); // to guarantee that thread is waiting passwordWaiter.wakeAll(); passwordLock.unlock(); } else if (e->type() == WalletOpenEventType) { TQString 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 ) { TQString 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); TQString 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) { TQApplication::beep(); } else if (e->type() == ServerCutEventType) { ServerCutEvent *sce = (ServerCutEvent*) e; TQString ctext = TQString::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(TQMouseEvent *e) { if (m_status != REMOTE_VIEW_CONNECTED) return; if (m_viewOnly) return; if ( e->type() != TQEvent::MouseMove ) { if ( (e->type() == TQEvent::MouseButtonPress) || (e->type() == TQEvent::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() == TQEvent::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(TQMouseEvent *e) { mouseEvent(e); e->accept(); } void KVncView::mouseDoubleClickEvent(TQMouseEvent *e) { mouseEvent(e); e->accept(); } void KVncView::mouseReleaseEvent(TQMouseEvent *e) { mouseEvent(e); e->accept(); } void KVncView::mouseMoveEvent(TQMouseEvent *e) { mouseEvent(e); e->ignore(); } void KVncView::wheelEvent(TQWheelEvent *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 TQWidget::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() { TQValueList<unsigned int> keys = m_mods.keys(); TQValueList<unsigned int>::const_iterator it = keys.begin(); while (it != keys.end()) { m_wthread.queueKeyEvent(*it, false); it++; } m_mods.clear(); } void KVncView::focusOutEvent(TQFocusEvent *) { unpressModifiers(); } TQSize 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; TQString 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; TQString 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 ) { TQApplication::postEvent(kvncview, new WalletOpenEvent()); passwordWaiter.wait(&passwordLock); //block if (!password.isNull()) retV = 1; //got it! } //Last try: Ask the user if (!retV) { TQApplication::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(); */ TQApplication::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() { TQApplication::postEvent(kvncview, new BeepEvent()); } extern void newServerCut(char *bytes, int length) { TQApplication::postEvent(kvncview, new ServerCutEvent(bytes, length)); } extern void postMouseEvent(int x, int y, int buttonMask) { TQApplication::postEvent(kvncview, new MouseStateEvent(x, y, buttonMask)); } #include "kvncview.moc"