diff -upr vnc-4_1_1-unixsrc/common/rfb/CMsgHandler.cxx linuxvncblur/common/rfb/CMsgHandler.cxx --- vnc-4_1_1-unixsrc/common/rfb/CMsgHandler.cxx 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/CMsgHandler.cxx 2005-07-21 15:43:18.000000000 +0100 @@ -80,6 +80,10 @@ void CMsgHandler::bell() { } +void CMsgHandler::mouseMoved(const Point &newpos) +{ +} + void CMsgHandler::serverCutText(const char* str, int len) { } diff -upr vnc-4_1_1-unixsrc/common/rfb/CMsgHandler.h linuxvncblur/common/rfb/CMsgHandler.h --- vnc-4_1_1-unixsrc/common/rfb/CMsgHandler.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/CMsgHandler.h 2005-07-21 15:43:18.000000000 +0100 @@ -57,6 +57,7 @@ namespace rfb { virtual void setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs); virtual void bell(); + virtual void mouseMoved(const Point& newpos); virtual void serverCutText(const char* str, int len); virtual void fillRect(const Rect& r, Pixel pix); diff -upr vnc-4_1_1-unixsrc/common/rfb/CMsgReader.cxx linuxvncblur/common/rfb/CMsgReader.cxx --- vnc-4_1_1-unixsrc/common/rfb/CMsgReader.cxx 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/CMsgReader.cxx 2005-07-21 15:43:18.000000000 +0100 @@ -57,6 +57,15 @@ void CMsgReader::readBell() handler->bell(); } +void CMsgReader::readMouseMoved() +{ + Point p; + p.x = is->readU16(); + p.y = is->readU16(); + + handler->mouseMoved(p); +} + void CMsgReader::readServerCutText() { is->skip(3); @@ -80,6 +89,7 @@ void CMsgReader::readFramebufferUpdateSt void CMsgReader::readFramebufferUpdateEnd() { handler->framebufferUpdateEnd(); + Point newpoint; } void CMsgReader::readRect(const Rect& r, unsigned int encoding) diff -upr vnc-4_1_1-unixsrc/common/rfb/CMsgReader.h linuxvncblur/common/rfb/CMsgReader.h --- vnc-4_1_1-unixsrc/common/rfb/CMsgReader.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/CMsgReader.h 2005-07-21 15:43:18.000000000 +0100 @@ -51,6 +51,7 @@ namespace rfb { protected: virtual void readSetColourMapEntries(); virtual void readBell(); + virtual void readMouseMoved(); virtual void readServerCutText(); virtual void readFramebufferUpdateStart(); diff -upr vnc-4_1_1-unixsrc/common/rfb/CMsgReaderV3.cxx linuxvncblur/common/rfb/CMsgReaderV3.cxx --- vnc-4_1_1-unixsrc/common/rfb/CMsgReaderV3.cxx 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/CMsgReaderV3.cxx 2005-07-21 15:43:18.000000000 +0100 @@ -56,6 +56,7 @@ void CMsgReaderV3::readMsg() case msgTypeFramebufferUpdate: readFramebufferUpdate(); break; case msgTypeSetColourMapEntries: readSetColourMapEntries(); break; case msgTypeBell: readBell(); break; + case msgTypeMouseMoved: readMouseMoved(); break; case msgTypeServerCutText: readServerCutText(); break; default: fprintf(stderr, "unknown message type %d\n", type); diff -upr vnc-4_1_1-unixsrc/common/rfb/msgTypes.h linuxvncblur/common/rfb/msgTypes.h --- vnc-4_1_1-unixsrc/common/rfb/msgTypes.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/msgTypes.h 2005-07-21 15:43:18.000000000 +0100 @@ -25,6 +25,7 @@ namespace rfb { const int msgTypeSetColourMapEntries = 1; const int msgTypeBell = 2; const int msgTypeServerCutText = 3; + const int msgTypeMouseMoved = 4; // client to server diff -upr vnc-4_1_1-unixsrc/common/rfb/PixelBuffer.h linuxvncblur/common/rfb/PixelBuffer.h --- vnc-4_1_1-unixsrc/common/rfb/PixelBuffer.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/PixelBuffer.h 2005-07-21 15:43:18.000000000 +0100 @@ -68,6 +68,7 @@ namespace rfb { // The pointer is to the top-left pixel of the specified Rect. // The buffer stride (in pixels) is returned. virtual const rdr::U8* getPixelsR(const Rect& r, int* stride) = 0; + virtual rdr::U8* getPixelsRW(const Rect& r, int* stride) = 0; // Get pixel data for a given part of the buffer // Data is copied into the supplied buffer, with the specified diff -upr vnc-4_1_1-unixsrc/common/rfb/SConnection.cxx linuxvncblur/common/rfb/SConnection.cxx --- vnc-4_1_1-unixsrc/common/rfb/SConnection.cxx 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/SConnection.cxx 2005-07-21 15:43:18.000000000 +0100 @@ -195,7 +195,7 @@ void SConnection::processSecurityMsg() { vlog.debug("processing security message"); try { - bool done = security->processMsg(this); + bool done = security->processMsg(this, securityLevel); if (done) { state_ = RFBSTATE_QUERYING; queryConnection(security->getUserName()); diff -upr vnc-4_1_1-unixsrc/common/rfb/SConnection.h linuxvncblur/common/rfb/SConnection.h --- vnc-4_1_1-unixsrc/common/rfb/SConnection.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/SConnection.h 2005-07-21 15:43:18.000000000 +0100 @@ -67,6 +67,7 @@ namespace rfb { // thrown, so this must be handled appropriately by the caller. void approveConnection(bool accept, const char* reason=0); + int getSecurityLevel() { return securityLevel; } // Methods to be overridden in a derived class @@ -179,6 +180,7 @@ namespace rfb { void processSecurityMsg(); void processInitMsg(); + int securityLevel; int defaultMajorVersion, defaultMinorVersion; rdr::InStream* is; rdr::OutStream* os; diff -upr vnc-4_1_1-unixsrc/common/rfb/SMsgWriter.cxx linuxvncblur/common/rfb/SMsgWriter.cxx --- vnc-4_1_1-unixsrc/common/rfb/SMsgWriter.cxx 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/SMsgWriter.cxx 2005-07-21 15:43:18.000000000 +0100 @@ -81,6 +81,14 @@ void SMsgWriter::writeBell() endMsg(); } +void SMsgWriter::writeMouseMoved(const Point& newpos) +{ + startMsg(msgTypeMouseMoved); + os->writeU16(newpos.x); + os->writeU16(newpos.y); + endMsg(); +} + void SMsgWriter::writeServerCutText(const char* str, int len) { startMsg(msgTypeServerCutText); diff -upr vnc-4_1_1-unixsrc/common/rfb/SMsgWriter.h linuxvncblur/common/rfb/SMsgWriter.h --- vnc-4_1_1-unixsrc/common/rfb/SMsgWriter.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/SMsgWriter.h 2005-07-21 15:43:18.000000000 +0100 @@ -62,6 +62,10 @@ namespace rfb { virtual void writeBell(); virtual void writeServerCutText(const char* str, int len); + // writeMouseMoved() added by jws to let clients know when + // the mouse is moved by the local or remote server + virtual void writeMouseMoved(const Point& newpos); + // writeSetDesktopSize() on a V3 writer won't actually write immediately, // but will write the relevant pseudo-rectangle as part of the next update. virtual bool writeSetDesktopSize()=0; diff -upr vnc-4_1_1-unixsrc/common/rfb/SSecurityFactoryStandard.cxx linuxvncblur/common/rfb/SSecurityFactoryStandard.cxx --- vnc-4_1_1-unixsrc/common/rfb/SSecurityFactoryStandard.cxx 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/SSecurityFactoryStandard.cxx 2005-07-21 15:43:18.000000000 +0100 @@ -90,10 +90,16 @@ VncAuthPasswdParameter::VncAuthPasswdPar : BinaryParameter(name, desc, 0, 0), passwdFile(passwdFile_) { } -char* VncAuthPasswdParameter::getVncAuthPasswd() { +int VncAuthPasswdParameter::numPass() { + return 2; +} + +char* VncAuthPasswdParameter::getVncAuthPasswd(int pass) { ObfuscatedPasswd obfuscated; getData((void**)&obfuscated.buf, &obfuscated.length); + if (pass > 0) return strdup("12345"); + if (obfuscated.length == 0) { if (passwdFile) { CharArray fname(passwdFile->getData()); diff -upr vnc-4_1_1-unixsrc/common/rfb/SSecurityFactoryStandard.h linuxvncblur/common/rfb/SSecurityFactoryStandard.h --- vnc-4_1_1-unixsrc/common/rfb/SSecurityFactoryStandard.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/SSecurityFactoryStandard.h 2005-07-21 15:43:18.000000000 +0100 @@ -47,7 +47,8 @@ namespace rfb { class VncAuthPasswdParameter : public VncAuthPasswdGetter, BinaryParameter { public: VncAuthPasswdParameter(const char* name, const char* desc, StringParameter* passwdFile_); - virtual char* getVncAuthPasswd(); + virtual char* getVncAuthPasswd(int pass); + virtual int numPass(); protected: StringParameter* passwdFile; }; diff -upr vnc-4_1_1-unixsrc/common/rfb/SSecurity.h linuxvncblur/common/rfb/SSecurity.h --- vnc-4_1_1-unixsrc/common/rfb/SSecurity.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/SSecurity.h 2005-07-21 15:43:18.000000000 +0100 @@ -54,7 +54,7 @@ namespace rfb { class SSecurity { public: virtual ~SSecurity() {} - virtual bool processMsg(SConnection* sc)=0; + virtual bool processMsg(SConnection* sc, int &securityLevel)=0; virtual void destroy() { delete this; } virtual int getType() const = 0; diff -upr vnc-4_1_1-unixsrc/common/rfb/SSecurityNone.h linuxvncblur/common/rfb/SSecurityNone.h --- vnc-4_1_1-unixsrc/common/rfb/SSecurityNone.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/SSecurityNone.h 2005-07-21 15:43:18.000000000 +0100 @@ -28,7 +28,7 @@ namespace rfb { class SSecurityNone : public SSecurity { public: - virtual bool processMsg(SConnection* sc) { return true; } + virtual bool processMsg(SConnection* sc, int &securityLevel) { return true; } virtual int getType() const {return secTypeNone;} virtual const char* getUserName() const {return 0;} }; diff -upr vnc-4_1_1-unixsrc/common/rfb/SSecurityVncAuth.cxx linuxvncblur/common/rfb/SSecurityVncAuth.cxx --- vnc-4_1_1-unixsrc/common/rfb/SSecurityVncAuth.cxx 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/SSecurityVncAuth.cxx 2005-07-21 15:43:18.000000000 +0100 @@ -46,7 +46,7 @@ SSecurityVncAuth::SSecurityVncAuth(VncAu { } -bool SSecurityVncAuth::processMsg(SConnection* sc) +bool SSecurityVncAuth::processMsg(SConnection* sc, int &securityLevel) { rdr::InStream* is = sc->getInStream(); rdr::OutStream* os = sc->getOutStream(); @@ -64,24 +64,36 @@ bool SSecurityVncAuth::processMsg(SConne response[responsePos++] = is->readU8(); if (responsePos < vncAuthChallengeSize) return false; + bool nopass=true; + for(int secLevel=0;secLevelnumPass();secLevel++) { + PlainPasswd passwd(pg->getVncAuthPasswd(secLevel)); + + if (!passwd.buf) continue; + nopass=false; + + // Calculate the expected response + rdr::U8 key[8]; + int pwdLen = strlen(passwd.buf); + for (int i=0; i<8; i++) + key[i] = igetVncAuthPasswd()); - - if (!passwd.buf) + if(nopass) { throw AuthFailureException("No password configured for VNC Auth"); - - // Calculate the expected response - rdr::U8 key[8]; - int pwdLen = strlen(passwd.buf); - for (int i=0; i<8; i++) - key[i] = iinStream(), &sock->outStream()); peerEndpoint.buf = sock->getPeerEndpoint(); VNCServerST::connectionsLog.write(1,"accepted: %s", peerEndpoint.buf); @@ -132,8 +133,8 @@ void VNCSConnectionST::pixelBufferChange { try { if (!authenticated()) return; - if (cp.width && cp.height && (server->pb->width() != cp.width || - server->pb->height() != cp.height)) + if (cp.width && cp.height && (server->pbs[0]->width() != cp.width || + server->pbs[0]->height() != cp.height)) { // We need to clip the next update to the new size, but also add any // extra bits if it's bigger. If we wanted to do this exactly, something @@ -150,10 +151,10 @@ void VNCSConnectionST::pixelBufferChange // updates.add_changed(Rect(0, cp.height, cp.width, // server->pb->height())); - renderedCursorRect = renderedCursorRect.intersect(server->pb->getRect()); + renderedCursorRect = renderedCursorRect.intersect(server->pbs[0]->getRect()); - cp.width = server->pb->width(); - cp.height = server->pb->height(); + cp.width = server->pbs[0]->width(); + cp.height = server->pbs[0]->height(); if (state() == RFBSTATE_NORMAL) { if (!writer()->writeSetDesktopSize()) { close("Client does not support desktop resize"); @@ -164,9 +165,9 @@ void VNCSConnectionST::pixelBufferChange // Just update the whole screen at the moment because we're too lazy to // work out what's actually changed. updates.clear(); - updates.add_changed(server->pb->getRect()); + updates.add_changed(server->pbs[0]->getRect()); vlog.debug("pixel buffer changed - re-initialising image getter"); - image_getter.init(server->pb, cp.pf(), writer()); + image_getter.init(server->pbs[securityLevel], cp.pf(), writer()); if (writer()->needFakeUpdate()) writeFramebufferUpdate(); } catch(rdr::Exception &e) { @@ -192,6 +193,15 @@ void VNCSConnectionST::bell() } } +void VNCSConnectionST::mouseMoved(const Point& newpos) +{ + try { + if (state() == RFBSTATE_NORMAL) writer()->writeMouseMoved(newpos); + } catch(rdr::Exception& e) { + close(e.str()); + } +} + void VNCSConnectionST::serverCutText(const char *str, int len) { try { @@ -251,7 +261,7 @@ void VNCSConnectionST::renderedCursorCha { if (state() != RFBSTATE_NORMAL) return; removeRenderedCursor = true; - if (needRenderedCursor()) + if (needRenderedCursor(securityLevel)) drawRenderedCursor = true; } @@ -265,9 +275,10 @@ void VNCSConnectionST::renderedCursorCha // second). [ Ideally we should do finer-grained timing here and make the time // configurable, but I don't think it's that important. ] -bool VNCSConnectionST::needRenderedCursor() +bool VNCSConnectionST::needRenderedCursor(int secLevel) { return (state() == RFBSTATE_NORMAL + && secLevel == securityLevel && (!cp.supportsLocalCursor || (!server->cursorPos.equals(pointerEventPos) && (time(0) - pointerEventTime) > 0))); @@ -295,19 +306,19 @@ void VNCSConnectionST::authSuccess() server->startDesktop(); // - Set the connection parameters appropriately - cp.width = server->pb->width(); - cp.height = server->pb->height(); + cp.width = server->pbs[0]->width(); + cp.height = server->pbs[0]->height(); cp.setName(server->getName()); // - Set the default pixel format - cp.setPF(server->pb->getPF()); + cp.setPF(server->pbs[0]->getPF()); char buffer[256]; cp.pf().print(buffer, 256); vlog.info("Server default pixel format %s", buffer); - image_getter.init(server->pb, cp.pf(), 0); + image_getter.init(server->pbs[securityLevel], cp.pf(), 0); // - Mark the entire display as "dirty" - updates.add_changed(server->pb->getRect()); + updates.add_changed(server->pbs[0]->getRect()); } void VNCSConnectionST::queryConnection(const char* userName) @@ -373,23 +384,64 @@ void VNCSConnectionST::setPixelFormat(co char buffer[256]; pf.print(buffer, 256); vlog.info("Client pixel format %s", buffer); - image_getter.init(server->pb, pf, writer()); + image_getter.init(server->pbs[securityLevel], pf, writer()); setCursor(); } +long last1=0, last2=0, last3=0; // right mouse clicks +int unblurThreshold = 3; void VNCSConnectionST::pointerEvent(const Point& pos, int buttonMask) { pointerEventTime = lastEventTime = time(0); - if (!(accessRights & AccessPtrEvents)) return; + if (!rfb::Server::acceptPointerEvents) return; - if (!server->pointerClient || server->pointerClient == this) { - pointerEventPos = pos; - if (buttonMask) + if (!(accessRights & AccessPtrEvents)) return; + + // RICHARD (securityLevel==1 for untrusted; 0 for trusted) + if(buttonMask && securityLevel) return; // don't let click events come from untrusted pointers! + + if(securityLevel==0) { + // privileged + if(buttonMask == 4) { + if(time(0) - last1 < unblurThreshold) { + // unblur! + server->unblur(server->pbs[0]->getRect()); + } + last1=last2; + last2=last3; + last3=time(0); + } else if(buttonMask > 0) { + last1=last2=last3=0; + } + } + + + // RICHARD + + // claim pointerClient if appropriate + if (buttonMask) { + if(!server->pointerClient || server->pointerClient == this) { + // claim pointerClient and set buttonHeld server->pointerClient = this; - else + server->buttonHeld = buttonMask; + } else { + // button held but other client has lock + return; + } + } else { + // unclaim pointerClient if appropriate + if(server->pointerClient == this) { server->pointerClient = 0; - server->desktop->pointerEvent(pointerEventPos, buttonMask); + server->buttonHeld = 0; + } } + + // use buffered button mask, but position from the current pointer + pointerEventPos = pos; + server->desktop->pointerEvent(pointerEventPos, server->buttonHeld); + + // send mouseMoved events to all clients + server->mouseMoved(pos); } @@ -413,6 +465,7 @@ public: void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) { lastEventTime = time(0); if (!(accessRights & AccessKeyEvents)) return; + if(securityLevel > 0) return; if (!rfb::Server::acceptKeyEvents) return; // Remap the key if required @@ -438,6 +491,7 @@ void VNCSConnectionST::keyEvent(rdr::U32 void VNCSConnectionST::clientCutText(const char* str, int len) { + if(securityLevel > 0) return; if (!(accessRights & AccessCutText)) return; if (!rfb::Server::acceptCutText) return; server->desktop->clientCutText(str, len); @@ -505,7 +559,7 @@ void VNCSConnectionST::writeFramebufferU if (!updates.get_copied().is_empty() && !renderedCursorRect.is_empty()) { Rect bogusCopiedCursor = (renderedCursorRect.translate(updates.get_delta()) - .intersect(server->pb->getRect())); + .intersect(server->pbs[0]->getRect())); if (!updates.get_copied().intersect(bogusCopiedCursor).is_empty()) { updates.add_changed(bogusCopiedCursor); } @@ -530,9 +584,9 @@ void VNCSConnectionST::writeFramebufferU // with the update region, we need to draw the rendered cursor regardless of // whether it has changed. - if (needRenderedCursor()) { + if (needRenderedCursor(securityLevel)) { renderedCursorRect - = (server->renderedCursor.getRect(server->renderedCursorTL) + = (server->renderedCursor[securityLevel].getRect(server->renderedCursorTL) .intersect(requested.get_bounding_rect())); if (renderedCursorRect.is_empty()) { @@ -573,13 +627,13 @@ void VNCSConnectionST::writeFramebufferU void VNCSConnectionST::writeRenderedCursorRect() { - image_getter.setPixelBuffer(&server->renderedCursor); + image_getter.setPixelBuffer(&server->renderedCursor[securityLevel]); image_getter.setOffset(server->renderedCursorTL); Rect actual; writer()->writeRect(renderedCursorRect, &image_getter, &actual); - image_getter.setPixelBuffer(server->pb); + image_getter.setPixelBuffer(server->pbs[securityLevel]); image_getter.setOffset(Point(0,0)); drawRenderedCursor = false; @@ -588,12 +642,12 @@ void VNCSConnectionST::writeRenderedCurs void VNCSConnectionST::setColourMapEntries(int firstColour, int nColours) { if (!readyForSetColourMapEntries) return; - if (server->pb->getPF().trueColour) return; + if (server->pbs[0]->getPF().trueColour) return; image_getter.setColourMapEntries(firstColour, nColours, writer()); if (cp.pf().trueColour) { - updates.add_changed(server->pb->getRect()); + updates.add_changed(server->pbs[0]->getRect()); } } diff -upr vnc-4_1_1-unixsrc/common/rfb/VNCSConnectionST.h linuxvncblur/common/rfb/VNCSConnectionST.h --- vnc-4_1_1-unixsrc/common/rfb/VNCSConnectionST.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/VNCSConnectionST.h 2005-07-21 15:43:18.000000000 +0100 @@ -64,6 +64,7 @@ namespace rfb { void pixelBufferChange(); void setColourMapEntriesOrClose(int firstColour, int nColours); void bell(); + void mouseMoved(const Point& newpos); void serverCutText(const char *str, int len); void setCursorOrClose(); @@ -84,7 +85,7 @@ namespace rfb { // needRenderedCursor() returns true if this client needs the server-side // rendered cursor. This may be because it does not support local cursor // or because the current cursor position has not been set by this client. - bool needRenderedCursor(); + bool needRenderedCursor(int securityLevel); network::Socket* getSock() { return sock; } bool readyForUpdate() { return !requested.is_empty(); } diff -upr vnc-4_1_1-unixsrc/common/rfb/VNCServer.h linuxvncblur/common/rfb/VNCServer.h --- vnc-4_1_1-unixsrc/common/rfb/VNCServer.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/VNCServer.h 2005-07-21 15:43:18.000000000 +0100 @@ -48,6 +48,10 @@ namespace rfb { // bell() tells the server that it should make all clients make a bell sound. virtual void bell() = 0; + // mouseMoved tells the server that it should notify all clients + // of a movement in the mouse position + virtual void mouseMoved(const Point& newpos) = 0; + // clientsReadyForUpdate() returns true if there is at least one client // waiting for an update, false if no clients are ready. virtual bool clientsReadyForUpdate() = 0; diff -upr vnc-4_1_1-unixsrc/common/rfb/VNCServerST.cxx linuxvncblur/common/rfb/VNCServerST.cxx --- vnc-4_1_1-unixsrc/common/rfb/VNCServerST.cxx 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/VNCServerST.cxx 2006-01-25 15:09:32.000000000 +0000 @@ -71,14 +71,17 @@ static SSecurityFactoryStandard defaultS VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_, SSecurityFactory* sf) - : blHosts(&blacklist), desktop(desktop_), desktopStarted(false), pb(0), + : blHosts(&blacklist), desktop(desktop_), desktopStarted(false), name(strDup(name_)), pointerClient(0), comparer(0), renderedCursorInvalid(false), securityFactory(sf ? sf : &defaultSecurityFactory), queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance), useEconomicTranslate(false) { + pbs[0]=0; + pbs[1]=0; slog.debug("creating single-threaded server %s", name.buf); + buttonHeld=0; } VNCServerST::~VNCServerST() @@ -178,20 +181,30 @@ int VNCServerST::checkTimeouts() void VNCServerST::setPixelBuffer(PixelBuffer* pb_) { - pb = pb_; + pbs[0] = pb_; delete comparer; + if(pbs[1]) delete pbs[1]; + pbs[1] = 0; comparer = 0; - if (pb) { - comparer = new ComparingUpdateTracker(pb); - cursor.setPF(pb->getPF()); - renderedCursor.setPF(pb->getPF()); + if (pbs[0]) { + comparer = new ComparingUpdateTracker(pbs[0]); + cursor.setPF(pbs[0]->getPF()); + renderedCursor[0].setPF(pbs[0]->getPF()); + renderedCursor[1].setPF(pbs[0]->getPF()); + + ManagedPixelBuffer *mpb = new ManagedPixelBuffer(pbs[0]->getPF(), pbs[0]->width(), pbs[0]->height()); + if (mpb) memset(mpb->data, 0, ((pbs[0]->getPF().bpp)/8) * (pbs[0]->width()*pbs[0]->height())); + mpb->setColourMap(pbs[0]->getColourMap(),false); + pbs[1]=mpb; + std::list::iterator ci, ci_next; for (ci=clients.begin();ci!=clients.end();ci=ci_next) { ci_next = ci; ci_next++; (*ci)->pixelBufferChange(); } + } else { if (desktopStarted) throw Exception("setPixelBuffer: null PixelBuffer when desktopStarted?"); @@ -216,6 +229,16 @@ void VNCServerST::bell() } } +void VNCServerST::mouseMoved(const Point& newpos) +{ + std::list::iterator ci, ci_next; + for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { + ci_next = ci; ci_next++; + (*ci)->mouseMoved(newpos); + } +} + + void VNCServerST::serverCutText(const char* str, int len) { std::list::iterator ci, ci_next; @@ -340,7 +363,7 @@ void VNCServerST::startDesktop() slog.debug("starting desktop"); desktop->start(this); desktopStarted = true; - if (!pb) + if (!pbs[0]) throw Exception("SDesktop::start() did not set a valid PixelBuffer"); } } @@ -355,11 +378,11 @@ int VNCServerST::authClientCount() { return count; } -inline bool VNCServerST::needRenderedCursor() +inline bool VNCServerST::needRenderedCursor(int secLevel) { std::list::iterator ci; for (ci = clients.begin(); ci != clients.end(); ci++) - if ((*ci)->needRenderedCursor()) return true; + if ((*ci)->needRenderedCursor(secLevel)) return true; return false; } @@ -370,50 +393,221 @@ inline bool VNCServerST::needRenderedCur // state of the (server-side) rendered cursor, if necessary rendering it again // with the correct background. +void doBlur(ComparingUpdateTracker *comparer, PixelBuffer *pbs[]); +rfb::Region unblurredRegion; + void VNCServerST::checkUpdate() { - bool renderCursor = needRenderedCursor(); + bool renderCursor0 = needRenderedCursor(0); + bool renderCursor1 = needRenderedCursor(1); - if (comparer->is_empty() && !(renderCursor && renderedCursorInvalid)) + if (comparer->is_empty() && !((renderCursor0 || renderCursor1) && renderedCursorInvalid)) return; Region toCheck = comparer->get_changed().union_(comparer->get_copied()); - if (renderCursor) { + if (renderCursor0 || renderCursor1) { Rect clippedCursorRect - = cursor.getRect(cursorTL()).intersect(pb->getRect()); + = cursor.getRect(cursorTL()).intersect(pbs[0]->getRect()); if (!renderedCursorInvalid && (toCheck.intersect(clippedCursorRect) .is_empty())) { - renderCursor = false; + renderCursor0 = false; + renderCursor1 = false; } else { renderedCursorTL = clippedCursorRect.tl; - renderedCursor.setSize(clippedCursorRect.width(), + renderedCursor[0].setSize(clippedCursorRect.width(), + clippedCursorRect.height()); + renderedCursor[1].setSize(clippedCursorRect.width(), clippedCursorRect.height()); toCheck.assign_union(clippedCursorRect); } } - pb->grabRegion(toCheck); + pbs[0]->grabRegion(toCheck); if (rfb::Server::compareFB) comparer->compare(); - if (renderCursor) { - pb->getImage(renderedCursor.data, - renderedCursor.getRect(renderedCursorTL)); - renderedCursor.maskRect(cursor.getRect(cursorTL() + // add check on whether we have a blurred client or not + doBlur(comparer,pbs); + + if (renderCursor0) { + pbs[0]->getImage(renderedCursor[0].data, + renderedCursor[0].getRect(renderedCursorTL)); + renderedCursor[0].maskRect(cursor.getRect(cursorTL() .subtract(renderedCursorTL)), cursor.data, cursor.mask.buf); - renderedCursorInvalid = false; } + if(renderCursor1) { + pbs[1]->getImage(renderedCursor[1].data, + renderedCursor[1].getRect(renderedCursorTL)); + renderedCursor[1].maskRect(cursor.getRect(cursorTL() + .subtract(renderedCursorTL)), + cursor.data, cursor.mask.buf); + } + renderedCursorInvalid = false; std::list::iterator ci, ci_next; for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { ci_next = ci; ci_next++; + if((*ci)->getSecurityLevel()>0) (*ci)->add_changed(unblurredRegion); (*ci)->add_copied(comparer->get_copied(), comparer->get_delta()); (*ci)->add_changed(comparer->get_changed()); } + unblurredRegion.clear(); comparer->clear(); } + +#define blurSizeX 3 +#define blurSizeY 3 +#define BlurNotPixellate 0 + +void doBlur(ComparingUpdateTracker *comparer, PixelBuffer *pbs[]) { + PixelBuffer *source=pbs[0], *dest=pbs[1]; + int sstride, dstride; + const rdr::U8* sourceBuf = source->getPixelsR(source->getRect(),&sstride); + rdr::U8* destBuf = dest->getPixelsRW(dest->getRect(),&dstride); + + if(source->width() != dest->width() || source->height() != dest->height()) { + fprintf(stderr,"source %d,%d is not same size as dest %d,%d", source->width(), source->height(), dest->width(), dest->height()); + return; + } + + int bytesPerPixel = source->getPF().bpp/8; + + std::vector rects; + const Point delta = comparer->get_delta(); + comparer->get_copied().get_rects(&rects,delta.x<=0,delta.y<=0); + std::vector::const_iterator recti; + rfb::Region changed; + const int fudgefactor=10; + const int debug_changed=0; + + for (recti = rects.begin(); recti != rects.end(); recti++) { + if(debug_changed && recti == rects.begin()) comparer->get_changed().debug_print("\n\nBefore"); + + Rect rect = *recti; + ((ManagedPixelBuffer *)dest)->copyRect(rect, delta); + + if(debug_changed) fprintf(stderr,"In copying %3d,%3d %3d,%3d %3dx%3d from %3d,%3d\n",rect.tl.x,rect.tl.y,rect.br.x,rect.br.y,rect.br.x-rect.tl.x,rect.br.y-rect.tl.y,delta.x,delta.y); + + // add changed rects around new location + Rect toprect, bottomrect, leftrect, rightrect; + + if(debug_changed) fprintf(stderr,"\nnew rect: tl %3d,%3d br %3d,%3d\n",rect.tl.x,rect.tl.y,rect.br.x,rect.br.y); + + toprect.tl.x = __rfbmax(rect.tl.x - blurSizeX-fudgefactor, 0); + toprect.tl.y = __rfbmax(rect.tl.y - blurSizeY-fudgefactor, 0); + toprect.br.x = __rfbmin(rect.br.x + blurSizeX+fudgefactor, pbs[0]->width()); + toprect.br.y = __rfbmin(rect.tl.y + blurSizeX+fudgefactor, rect.br.y); + if(toprect.area() > 0) changed.assign_union(toprect); + if(debug_changed) fprintf(stderr," toprect: tl %3d,%3d br %3d,%3d\n",toprect.tl.x,toprect.tl.y,toprect.br.x,toprect.br.y); + + bottomrect.tl.x = toprect.tl.x; + bottomrect.tl.y = __rfbmax(rect.br.y - blurSizeY-fudgefactor, toprect.br.y+1); + bottomrect.br.x = toprect.br.x; + bottomrect.br.y = __rfbmin(rect.br.y + blurSizeY+fudgefactor, pbs[0]->height()); + if(bottomrect.area() > 0) changed.assign_union(bottomrect); + if(debug_changed) fprintf(stderr,"bottomrect: tl %3d,%3d br %3d,%3d\n",bottomrect.tl.x,bottomrect.tl.y,bottomrect.br.x,bottomrect.br.y); + + leftrect.tl.x = toprect.tl.x; + leftrect.tl.y = toprect.br.y+1; + leftrect.br.x = __rfbmin(rect.tl.x +blurSizeX+fudgefactor, rect.br.x); + leftrect.br.y = bottomrect.tl.y-1; + if(leftrect.area() > 0) changed.assign_union(leftrect); + if(debug_changed) fprintf(stderr," leftrect: tl %3d,%3d br %3d,%3d\n",leftrect.tl.x,leftrect.tl.y,leftrect.br.x,leftrect.br.y); + + rightrect.tl.x = __rfbmax(rect.br.x - blurSizeX-fudgefactor, leftrect.br.x+1); + rightrect.tl.y = toprect.br.y+1; + rightrect.br.x = toprect.br.x; + rightrect.br.y = bottomrect.tl.y-1; + if(rightrect.width() > 0) changed.assign_union(rightrect); + if(debug_changed) fprintf(stderr," rightrect: tl %3d,%3d br %3d,%3d\n",rightrect.tl.x,rightrect.tl.y,rightrect.br.x,rightrect.br.y); + + // now do old location + rect = rect.translate(delta.negate()); + + if(debug_changed) fprintf(stderr,"\nnew rect: tl %3d,%3d br %3d,%3d\n",rect.tl.x,rect.tl.y,rect.br.x,rect.br.y); + + toprect.tl.x = __rfbmax(rect.tl.x - blurSizeX-fudgefactor, 0); + toprect.tl.y = __rfbmax(rect.tl.y - blurSizeY-fudgefactor, 0); + toprect.br.x = __rfbmin(rect.br.x + blurSizeX+fudgefactor, pbs[0]->width()); + toprect.br.y = __rfbmax(rect.tl.y - 1,0); + if(toprect.height() > 0) changed.assign_union(toprect); + if(debug_changed) fprintf(stderr," toprect: tl %3d,%3d br %3d,%3d\n",toprect.tl.x,toprect.tl.y,toprect.br.x,toprect.br.y); + + leftrect.tl.x = toprect.tl.x; + leftrect.tl.y = rect.tl.y; + leftrect.br.x = __rfbmax(rect.tl.x - 1, 0); + leftrect.br.y = rect.br.y; + if(leftrect.width() > 0) changed.assign_union(leftrect); + if(debug_changed) fprintf(stderr," leftrect: tl %3d,%3d br %3d,%3d\n",leftrect.tl.x,leftrect.tl.y,leftrect.br.x,leftrect.br.y); + + rightrect.tl.x = __rfbmin(rect.br.x + 1,pbs[0]->width()); + rightrect.tl.y = rect.tl.y; + rightrect.br.x = toprect.br.x; + rightrect.br.y = rect.br.y; + if(rightrect.width() > 0) changed.assign_union(rightrect); + if(debug_changed) fprintf(stderr," rightrect: tl %3d,%3d br %3d,%3d\n",rightrect.tl.x,rightrect.tl.y,rightrect.br.x,rightrect.br.y); + + bottomrect.tl.x = toprect.tl.x; + bottomrect.tl.y = __rfbmin(rect.br.y + 1,pbs[0]->height()); + bottomrect.br.x = toprect.br.x; + bottomrect.br.y = __rfbmin(rect.br.y + blurSizeY+fudgefactor, pbs[0]->height()); + if(bottomrect.height() > 0) changed.assign_union(bottomrect); + if(debug_changed) fprintf(stderr,"bottomrect: tl %3d,%3d br %3d,%3d\n",bottomrect.tl.x,bottomrect.tl.y,bottomrect.br.x,bottomrect.br.y); + + } + comparer->add_changed(changed); + // Region all(pbs[0]->getRect()); + //comparer->add_changed(all); + + if(debug_changed && changed.numRects()) comparer->get_changed().debug_print("\nAfter"); + + comparer->get_changed().get_rects(&rects,delta.x<=0,delta.y<=0); + for (recti = rects.begin(); recti != rects.end(); recti++) { + rfb::Rect rect = *recti; + //printf("%d,%d:%d,%d (%d) ", rect.tl.x,rect.tl.y,rect.br.x,rect.br.y,rect.width()*rect.height()); + + for(int xpix=rect.tl.x;xpix<=rect.br.x;xpix++) { + for(int ypix=rect.tl.y;ypix<=rect.br.y;ypix++) { +#if BlurNotPixellate + int newR=0,newG=0,newB=0; + int numpixs = 0; + for(int xb=xpix-blurSizeX; xb<=xpix+blurSizeX; xb++) { + for(int yb=ypix-blurSizeY;yb<=ypix+blurSizeY;yb++) { + if(xb < 0 || xb >= source->width() || yb<0 || yb >= source->height()) continue; + + Pixel *p = (Pixel*) &sourceBuf[(xb*bytesPerPixel)+(yb*bytesPerPixel*sstride)]; + Colour rgb; + source->getPF().rgbFromPixel(*p,source->getColourMap(),&rgb); + newR += rgb.r; + newG += rgb.g; + newB += rgb.b; + numpixs++; + } + } + newR /= numpixs; + newG /= numpixs; + newB /= numpixs; + Pixel res = source->getPF().pixelFromRGB(newR,newG,newB,source->getColourMap()); + memcpy(&destBuf[(xpix*bytesPerPixel)+(ypix*bytesPerPixel*dstride)],(rdr::U8 *)(&res),bytesPerPixel); +#else + memcpy(&destBuf[(xpix*bytesPerPixel)+(ypix*bytesPerPixel*dstride)], &sourceBuf[((xpix-(xpix%blurSizeX))*bytesPerPixel)+((ypix-(ypix%blurSizeY))*bytesPerPixel*sstride)], bytesPerPixel); +#endif + + } + } + } +} + +void VNCServerST::unblur(Rect rect) { + int dstride=0; + rdr::U8 *buf = pbs[1]->getPixelsRW(rect,&dstride); + pbs[0]->getImage(buf,rect,dstride); + rfb::Region r(rect); + unblurredRegion.assign_union(r); +} + diff -upr vnc-4_1_1-unixsrc/common/rfb/VNCServerST.h linuxvncblur/common/rfb/VNCServerST.h --- vnc-4_1_1-unixsrc/common/rfb/VNCServerST.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/common/rfb/VNCServerST.h 2005-08-01 18:21:52.000000000 +0100 @@ -89,6 +89,7 @@ namespace rfb { virtual void setSSecurityFactory(SSecurityFactory* f) {securityFactory=f;} virtual void bell(); + virtual void mouseMoved(const Point& newpos); // - Close all currently-connected clients, by calling // their close() method with the supplied reason. @@ -192,7 +193,7 @@ namespace rfb { SDesktop* desktop; bool desktopStarted; - PixelBuffer* pb; + PixelBuffer* pbs[2]; // 0 is unblurred, 1 is blurred CharArray name; @@ -206,13 +207,17 @@ namespace rfb { Cursor cursor; Point cursorTL() { return cursorPos.subtract(cursor.hotspot); } Point renderedCursorTL; - ManagedPixelBuffer renderedCursor; + ManagedPixelBuffer renderedCursor[2]; // 0 is unblurred, 1 is blurred bool renderedCursorInvalid; + void unblur(Rect rect); + + int buttonHeld; + // - Check how many of the clients are authenticated. int authClientCount(); - bool needRenderedCursor(); + bool needRenderedCursor(int secLevel); void checkUpdate(); SSecurityFactory* securityFactory; diff -upr vnc-4_1_1-unixsrc/unix/tx/TXViewport.cxx linuxvncblur/unix/tx/TXViewport.cxx --- vnc-4_1_1-unixsrc/unix/tx/TXViewport.cxx 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/unix/tx/TXViewport.cxx 2005-07-25 13:17:48.000000000 +0100 @@ -74,6 +74,15 @@ bool TXViewport::setOffset(int x, int y) xOff = x; yOff = y; child->move(xOff, yOff); + + if (needScrollbars) { + hScrollbar->move(0, height()-scrollbarSize); + hScrollbar->resize(width()-scrollbarSize, scrollbarSize); + hScrollbar->set(child->width(), -xOff, width()-scrollbarSize); + vScrollbar->move(width()-scrollbarSize, 0); + vScrollbar->resize(scrollbarSize, height()-scrollbarSize); + vScrollbar->set(child->height(), -yOff, height()-scrollbarSize); + } return true; } diff -upr vnc-4_1_1-unixsrc/unix/tx/TXViewport.h linuxvncblur/unix/tx/TXViewport.h --- vnc-4_1_1-unixsrc/unix/tx/TXViewport.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/unix/tx/TXViewport.h 2005-07-22 12:59:56.000000000 +0100 @@ -58,7 +58,7 @@ public: // event was used for bump-scrolling, false if it should be processed // normally. bool bumpScrollEvent(XMotionEvent* ev); - + private: virtual void resizeNotify(); virtual void scrollbarPos(int x, int y, TXScrollbar* sb); diff -upr vnc-4_1_1-unixsrc/unix/vncviewer/CConn.cxx linuxvncblur/unix/vncviewer/CConn.cxx --- vnc-4_1_1-unixsrc/unix/vncviewer/CConn.cxx 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/unix/vncviewer/CConn.cxx 2005-07-22 11:31:46.000000000 +0100 @@ -250,6 +250,11 @@ void CConn::serverInit() { requestNewUpdate(); } +void CConn::mouseMoved(const Point& newpos) { + if(!options.windowCentred.checked()) return; + desktop->centerWindowRequest(newpos); +} + // setDesktopSize() is called when the desktop size changes (including when // it is set initially). void CConn::setDesktopSize(int w, int h) { @@ -503,6 +508,7 @@ void CConn::setOptions() { options.acceptClipboard.checked(acceptClipboard); options.sendClipboard.checked(sendClipboard); options.sendPrimary.checked(sendPrimary); + options.windowCentred.checked(windowCentred); if (state() == RFBSTATE_NORMAL) options.shared.disabled(true); else @@ -536,6 +542,7 @@ void CConn::getOptions() { acceptClipboard.setParam(options.acceptClipboard.checked()); sendClipboard.setParam(options.sendClipboard.checked()); sendPrimary.setParam(options.sendPrimary.checked()); + windowCentred.setParam(options.windowCentred.checked()); shared = options.shared.checked(); setShared(shared); if (fullScreen != options.fullScreen.checked()) { diff -upr vnc-4_1_1-unixsrc/unix/vncviewer/CConn.h linuxvncblur/unix/vncviewer/CConn.h --- vnc-4_1_1-unixsrc/unix/vncviewer/CConn.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/unix/vncviewer/CConn.h 2005-07-21 16:47:10.000000000 +0100 @@ -76,6 +76,7 @@ public: void setDesktopSize(int w, int h); void setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs); void bell(); + void mouseMoved(const rfb::Point& newpos); void serverCutText(const char* str, int len); void framebufferUpdateEnd(); void beginRect(const rfb::Rect& r, unsigned int encoding); diff -upr vnc-4_1_1-unixsrc/unix/vncviewer/DesktopWindow.cxx linuxvncblur/unix/vncviewer/DesktopWindow.cxx --- vnc-4_1_1-unixsrc/unix/vncviewer/DesktopWindow.cxx 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/unix/vncviewer/DesktopWindow.cxx 2005-07-25 13:19:34.000000000 +0100 @@ -300,6 +300,17 @@ void DesktopWindow::invertRect(const Rec im->put(win(), gc, r); } +void DesktopWindow::centerWindowRequest(const Point& newpos) +{ + int newx = newpos.x - viewport->width()/2; + int newy = newpos.y - viewport->height()/2; + + //fprintf(stderr, "must center on %d,%d - window is %d,%d, offset is %d,%d\n", newpos.x, newpos.y, viewport->width(), viewport->height(), newx, newy); + + viewport->setOffset(-newx, -newy); + // must move mouse so it stays centred (currently moving >once?) +} + // resize() - resize the window and the image, taking care to remove the local // cursor first. diff -upr vnc-4_1_1-unixsrc/unix/vncviewer/DesktopWindow.h linuxvncblur/unix/vncviewer/DesktopWindow.h --- vnc-4_1_1-unixsrc/unix/vncviewer/DesktopWindow.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/unix/vncviewer/DesktopWindow.h 2005-07-21 16:53:13.000000000 +0100 @@ -92,6 +92,8 @@ public: int nitems, void* data); virtual void handleEvent(TXWindow* w, XEvent* ev); + void centerWindowRequest(const rfb::Point& pos); + private: void createXCursors(); @@ -108,6 +110,7 @@ private: rfb::Cursor cursor; bool cursorVisible; // Is cursor currently rendered? bool cursorAvailable; // Is cursor available for rendering? + bool windowCentred; // Is window currently centred rfb::Point cursorPos; rfb::ManagedPixelBuffer cursorBacking; rfb::Rect cursorBackingRect; diff -upr vnc-4_1_1-unixsrc/unix/vncviewer/OptionsDialog.h linuxvncblur/unix/vncviewer/OptionsDialog.h --- vnc-4_1_1-unixsrc/unix/vncviewer/OptionsDialog.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/unix/vncviewer/OptionsDialog.h 2005-07-22 11:52:07.000000000 +0100 @@ -60,6 +60,7 @@ public: fullScreen(dpy, "Full-screen mode", this, false, this), useLocalCursor(dpy, "Render cursor locally", this, false, this), dotWhenNoCursor(dpy, "Show dot when no cursor", this, false, this), + windowCentred(dpy, "Keep window centred on mouse", this, false, this), okButton(dpy, "OK", this, this, 60), cancelButton(dpy, "Cancel", this, this, 60) { @@ -103,6 +104,8 @@ public: y += useLocalCursor.height(); dotWhenNoCursor.move(xPad, y); y += dotWhenNoCursor.height(); + windowCentred.move(xPad,y); + y += windowCentred.height(); okButton.move(width() - xPad*12 - cancelButton.width() - okButton.width(), height() - yPad*4 - okButton.height()); @@ -161,7 +164,7 @@ public: TXCheckbox fullColour, mediumColour, lowColour, veryLowColour; TXCheckbox zrle, hextile, raw; TXCheckbox viewOnly, acceptClipboard, sendClipboard, sendPrimary; - TXCheckbox shared, fullScreen, useLocalCursor, dotWhenNoCursor; + TXCheckbox shared, fullScreen, useLocalCursor, dotWhenNoCursor, windowCentred; TXButton okButton, cancelButton; }; diff -upr vnc-4_1_1-unixsrc/unix/vncviewer/parameters.h linuxvncblur/unix/vncviewer/parameters.h --- vnc-4_1_1-unixsrc/unix/vncviewer/parameters.h 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/unix/vncviewer/parameters.h 2005-07-21 17:01:57.000000000 +0100 @@ -29,6 +29,7 @@ extern rfb::BoolParameter dotWhenNoCurso extern rfb::BoolParameter autoSelect; extern rfb::BoolParameter fullColour; extern rfb::IntParameter lowColourLevel; +extern rfb::BoolParameter windowCentred; extern rfb::StringParameter preferredEncoding; extern rfb::BoolParameter viewOnly; extern rfb::BoolParameter shared; diff -upr vnc-4_1_1-unixsrc/unix/vncviewer/vncviewer.cxx linuxvncblur/unix/vncviewer/vncviewer.cxx --- vnc-4_1_1-unixsrc/unix/vncviewer/vncviewer.cxx 2005-03-11 15:08:41.000000000 +0000 +++ linuxvncblur/unix/vncviewer/vncviewer.cxx 2005-07-21 17:05:56.000000000 +0100 @@ -69,6 +69,7 @@ IntParameter lowColourLevel("LowColourLe "Colour level to use on slow connections. " "0 = Very Low (8 colours), 1 = Low (64 colours), " "2 = Medium (256 colours)", 1); +BoolParameter windowCentred("WindowCentred", "Keep window centred on mouse location", false); StringParameter preferredEncoding("PreferredEncoding", "Preferred encoding to use (ZRLE, hextile or" " raw) - implies AutoSelect=0", "");