You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdenetwork/krdc/vnc/kvncview.cpp

847 lines
21 KiB

/***************************************************************************
kvncview.cpp - main widget
-------------------
begin : Thu Dec 20 15:11:42 CET 2001
copyright : (C) 2015 Timothy Pearson <kb9vqf@pearsoncomputing.net>
(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 <tdeapplication.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kstandarddirs.h>
#include <kpassdlg.h>
#include <kdialogbase.h>
#include <tdewallet.h>
#include <tqdatastream.h>
#include <dcopclient.h>
#include <tqclipboard.h>
#include <tqbitmap.h>
#include <tqmutex.h>
#include <tqvbox.h>
#include <tqtimer.h>
#include <tqpainter.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.
*/
bool appDataConfigured = false;
Display* dpy;
static KVncView *kvncview;
//Passwords and TDEWallet data
extern TDEWallet::Wallet *wallet;
bool useTDEWallet = 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, TQt::WResizeNoErase | TQt::WRepaintNoErase | TQt::WStaticContents),
m_cthreadObject(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 = tqt_xdisplay();
setFixedSize(16,16);
setFocusPolicy(TQ_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()));
TDEStandardDirs *dirs = TDEGlobal::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(TQt::BlankCursor));
break;
case DOT_CURSOR_AUTO:
if (m_enableClientCursor)
setCursor(TQCursor(TQt::BlankCursor));
else
setCursor(m_cursor);
break;
}
}
TQString KVncView::host() {
return m_host;
}
int KVncView::port() {
return m_port;
}
void KVncView::startQuitting() {
m_quitFlag = true;
}
bool KVncView::isQuitting() {
return m_quitFlag;
}
void KVncView::configureApp(Quality q, const TQString specialEncodings) {
appDataConfigured = true;
m_cthreadObject.cl->appData.shareDesktop = 1;
m_cthreadObject.cl->appData.viewOnly = 0;
if (q == QUALITY_LOW) {
m_cthreadObject.cl->appData.useBGR233 = 1;
m_cthreadObject.cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw";
m_cthreadObject.cl->appData.compressLevel = 9;
m_cthreadObject.cl->appData.qualityLevel = 1;
m_cthreadObject.cl->appData.useRemoteCursor = 1;
}
else if (q == QUALITY_MEDIUM) {
m_cthreadObject.cl->appData.useBGR233 = 0;
m_cthreadObject.cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw";
m_cthreadObject.cl->appData.compressLevel = 5;
m_cthreadObject.cl->appData.qualityLevel = 7;
m_cthreadObject.cl->appData.useRemoteCursor = 1;
}
else if ((q == QUALITY_HIGH) || (q == QUALITY_UNKNOWN)) {
m_cthreadObject.cl->appData.useBGR233 = 0;
m_cthreadObject.cl->appData.encodingsString = "copyrect hextile raw";
m_cthreadObject.cl->appData.compressLevel = 0;
m_cthreadObject.cl->appData.qualityLevel = 9;
m_cthreadObject.cl->appData.useRemoteCursor = 1;
}
if (!specialEncodings.isNull()) {
m_cthreadObject.cl->appData.encodingsString = specialEncodings.latin1();
}
m_cthreadObject.cl->appData.nColours = 256;
m_cthreadObject.cl->appData.requestedDepth = 0;
}
bool KVncView::checkLocalKRfb() {
if ( m_host != "localhost" && !m_host.isEmpty() )
return true;
DCOPClient *d = TDEApplication::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 tdewallet = hp->useTDEWallet();
// 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->setUseTDEWallet(tdewallet);
if ( dlg->exec() == TQDialog::Rejected )
return false;
ci = prefs->quality();
hp->setAskOnConnect(prefs->showPrefs());
hp->setQuality(ci);
hp->setUseTDEWallet(prefs->useTDEWallet());
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);
useTDEWallet = hp->useTDEWallet();
}
setStatus(REMOTE_VIEW_CONNECTING);
m_cthreadObject.moveToThread(&m_cthread);
TQTimer::singleShot(0, &m_cthreadObject, SLOT(run()));
m_cthread.start();
setBackgroundMode(TQt::NoBackground);
return true;
}
KVncView::~KVncView()
{
startQuitting();
m_cthread.wait();
}
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;
m_dontSendCb = s;
if (s)
setCursor(TQt::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);
m_cthreadObject.setScaling(width(), height());
}
else {
setFixedSize(m_framebufferSize);
m_cthreadObject.setScaling(-1, -1);
}
// Force full redraw
drawRegion(0, 0, width(), height());
}
}
void KVncView::paintEvent(TQPaintEvent *e) {
drawRegion(e->rect().x(),
e->rect().y(),
e->rect().width(),
e->rect().height());
}
void KVncView::resizeEvent(TQResizeEvent *e) {
if (m_scaling) {
m_cthreadObject.setScaling(width(), height());
// Force full redraw
drawRegion(0, 0, width(), height());
}
}
void KVncView::drawRegion(int x, int y, int w, int h) {
TQPainter painter(this);
painter.drawImage(TQRect(x, y, w, h), m_cthreadObject.image(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() == 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 TDEWallet password if necessary.
if ( useTDEWallet && !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 = TDEWallet::Wallet::NetworkWallet();
wallet = TDEWallet::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. Verify the KDED TDE Internet Daemon is running at the remote server."),
i18n("Connection Failure"));
break;
case ERROR_AUTHENTICATION:
//Login failed: Remove wallet entry if there is one.
if ( useTDEWallet && 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, TQClipboard::Clipboard);
m_cb->setText(ctext, TQClipboard::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() & Qt::LeftButton )
m_buttonMask |= 0x01;
if ( e->button() & Qt::MidButton )
m_buttonMask |= 0x02;
if ( e->button() & Qt::RightButton )
m_buttonMask |= 0x04;
}
else if ( e->type() == TQEvent::MouseButtonRelease ) {
if ( e->button() & Qt::LeftButton )
m_buttonMask &= 0xfe;
if ( e->button() & Qt::MidButton )
m_buttonMask &= 0xfd;
if ( e->button() & Qt::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_cthreadObject.queueMouseEvent(x, y, m_buttonMask);
if (m_enableClientCursor) {
// FIXME
// How to draw soft cursor?
// 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_cthreadObject.queueMouseEvent(x, y, eb|m_buttonMask);
m_cthreadObject.queueMouseEvent(x, y, m_buttonMask);
e->accept();
}
void KVncView::pressKey(XEvent *xe) {
if (m_status != REMOTE_VIEW_CONNECTED)
return;
if (m_viewOnly)
return;
KKeyNative k(xe);
uint mod = k.mod();
if (mod & KKeyNative::modX(KKey::SHIFT))
m_cthreadObject.queueKeyEvent(XK_Shift_L, true);
if (mod & KKeyNative::modX(KKey::CTRL))
m_cthreadObject.queueKeyEvent(XK_Control_L, true);
if (mod & KKeyNative::modX(KKey::ALT))
m_cthreadObject.queueKeyEvent(XK_Alt_L, true);
if (mod & KKeyNative::modX(KKey::WIN))
m_cthreadObject.queueKeyEvent(XK_Meta_L, true);
m_cthreadObject.queueKeyEvent(k.sym(), true);
m_cthreadObject.queueKeyEvent(k.sym(), false);
if (mod & KKeyNative::modX(KKey::WIN))
m_cthreadObject.queueKeyEvent(XK_Meta_L, false);
if (mod & KKeyNative::modX(KKey::ALT))
m_cthreadObject.queueKeyEvent(XK_Alt_L, false);
if (mod & KKeyNative::modX(KKey::CTRL))
m_cthreadObject.queueKeyEvent(XK_Control_L, false);
if (mod & KKeyNative::modX(KKey::SHIFT))
m_cthreadObject.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_cthreadObject.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_cthreadObject.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(TQClipboard::Clipboard);
if (text.length() > MAX_SELECTION_LENGTH)
return;
m_cthreadObject.queueClientCut(text);
}
void KVncView::selectionChanged() {
if (m_status != REMOTE_VIEW_CONNECTED)
return;
if (m_cb->ownsSelection() || m_dontSendCb)
return;
TQString text = m_cb->text(TQClipboard::Selection);
if (text.length() > MAX_SELECTION_LENGTH)
return;
m_cthreadObject.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 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 && useTDEWallet ) {
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) {
passwd = strdup((const char*)password);
}
//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) {
/* TDEApplication::kApplication()->lock();
kvncview->drawRegion(x, y, width, height);
TDEApplication::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"