Migrating to new directory structure adopted from the RealVNC's source tree. More changes will follow.

git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@590 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/unix/vncviewer/AboutDialog.h b/unix/vncviewer/AboutDialog.h
new file mode 100644
index 0000000..c4f0a7c
--- /dev/null
+++ b/unix/vncviewer/AboutDialog.h
@@ -0,0 +1,42 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+//
+// AboutDialog.h
+//
+
+#ifndef __ABOUTDIALOG_H__
+#define __ABOUTDIALOG_H__
+
+#include "TXMsgBox.h"
+#include "parameters.h"
+
+#include <intl/gettext.h>
+#define _(String) gettext (String)
+#define gettext_noop(String) String
+#define N_(String) gettext_noop (String)
+
+extern char buildtime[];
+
+class AboutDialog : public TXMsgBox {
+public:
+  AboutDialog(Display* dpy)
+    : TXMsgBox(dpy, aboutText, MB_OK, _("About VNC Viewer")) {
+  }
+};
+
+#endif
diff --git a/unix/vncviewer/CConn.cxx b/unix/vncviewer/CConn.cxx
new file mode 100644
index 0000000..eabe33a
--- /dev/null
+++ b/unix/vncviewer/CConn.cxx
@@ -0,0 +1,749 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+//
+// CConn.cxx
+//
+
+#include <unistd.h>
+#include "CConn.h"
+#include <rfb/CMsgWriter.h>
+#include <rfb/encodings.h>
+#include <rfb/secTypes.h>
+#include <rfb/CSecurityNone.h>
+#include <rfb/CSecurityVncAuth.h>
+#include <rfb/Hostname.h>
+#include <rfb/LogWriter.h>
+#include <rfb/util.h>
+#include <rfb/Password.h>
+#include <network/TcpSocket.h>
+
+#include "TXViewport.h"
+#include "DesktopWindow.h"
+#include "ServerDialog.h"
+#include "PasswdDialog.h"
+#include "parameters.h"
+
+using namespace rfb;
+
+static rfb::LogWriter vlog("CConn");
+
+IntParameter debugDelay("DebugDelay","Milliseconds to display inverted "
+                        "pixel data - a debugging feature", 0);
+
+StringParameter menuKey("MenuKey", "The key which brings up the popup menu",
+                        "F8");
+StringParameter windowName("name", "The X window name", "");
+
+CConn::CConn(Display* dpy_, int argc_, char** argv_, network::Socket* sock_,
+             char* vncServerName, bool reverse)
+  : dpy(dpy_), argc(argc_),
+    argv(argv_), serverHost(0), serverPort(0), sock(sock_), viewport(0),
+    desktop(0), desktopEventHandler(0),
+    currentEncoding(encodingZRLE), lastServerEncoding((unsigned int)-1),
+    fullColour(::fullColour),
+    autoSelect(::autoSelect), shared(::shared), formatChange(false),
+    encodingChange(false), sameMachine(false), fullScreen(::fullScreen),
+    ctrlDown(false), altDown(false),
+    menuKeysym(0), menu(dpy, this), options(dpy, this), about(dpy), info(dpy),
+    reverseConnection(reverse)
+{
+  CharArray menuKeyStr(menuKey.getData());
+  menuKeysym = XStringToKeysym(menuKeyStr.buf);
+
+  setShared(shared);
+  addSecType(secTypeNone);
+  addSecType(secTypeVncAuth);
+  CharArray encStr(preferredEncoding.getData());
+  int encNum = encodingNum(encStr.buf);
+  if (encNum != -1) {
+    currentEncoding = encNum;
+  }
+  cp.supportsDesktopResize = true;
+  cp.supportsLocalCursor = useLocalCursor;
+  cp.customCompressLevel = customCompressLevel;
+  cp.compressLevel = compressLevel;
+  cp.noJpeg = noJpeg;
+  cp.qualityLevel = qualityLevel;
+  initMenu();
+
+  if (sock) {
+    char* name = sock->getPeerEndpoint();
+    vlog.info("Accepted connection from %s", name);
+    if (name) free(name);
+  } else {
+    if (vncServerName) {
+      getHostAndPort(vncServerName, &serverHost, &serverPort);
+    } else {
+      ServerDialog dlg(dpy, &options, &about);
+      if (!dlg.show() || dlg.entry.getText()[0] == 0) {
+        exit(1);
+      }
+      getHostAndPort(dlg.entry.getText(), &serverHost, &serverPort);
+    }
+
+    sock = new network::TcpSocket(serverHost, serverPort);
+    vlog.info("connected to host %s port %d", serverHost, serverPort);
+  }
+
+  sameMachine = sock->sameMachine();
+  sock->inStream().setBlockCallback(this);
+  setServerName(sock->getPeerEndpoint());
+  setStreams(&sock->inStream(), &sock->outStream());
+  initialiseProtocol();
+}
+
+CConn::~CConn() {
+  free(serverHost);
+  delete desktop;
+  delete viewport;
+  delete sock;
+}
+
+// deleteWindow() is called when the user closes the desktop or menu windows.
+
+void CConn::deleteWindow(TXWindow* w) {
+  if (w == &menu) {
+    menu.unmap();
+  } else if (w == viewport) {
+    exit(1);
+  }
+}
+
+// handleEvent() filters all events on the desktop and menu.  Most are passed
+// straight through.  The exception is the F8 key.  When pressed on the
+// desktop, it is used to bring up the menu.  An F8 press or release on the
+// menu is passed through as if it were on the desktop.
+
+void CConn::handleEvent(TXWindow* w, XEvent* ev)
+{
+  KeySym ks;
+  char str[256];
+
+  switch (ev->type) {
+  case KeyPress:
+  case KeyRelease:
+    XLookupString(&ev->xkey, str, 256, &ks, NULL);
+    if (ks == menuKeysym && (ev->xkey.state & (ShiftMask|ControlMask)) == 0) {
+      if (w == desktop && ev->type == KeyPress) {
+        showMenu(ev->xkey.x_root, ev->xkey.y_root);
+        break;
+      } else if (w == &menu) {
+        if (ev->type == KeyPress) menu.unmap();
+        desktopEventHandler->handleEvent(w, ev);
+        break;
+      }
+    }
+    // drop through
+
+  default:
+    if (w == desktop) desktopEventHandler->handleEvent(w, ev);
+    else if (w == &menu) menuEventHandler->handleEvent(w, ev);
+  }
+}
+
+// blockCallback() is called when reading from the socket would block.  We
+// process X events until the socket is ready for reading again.
+
+void CConn::blockCallback() {
+  fd_set rfds;
+  do {
+    struct timeval tv;
+    struct timeval* tvp = 0;
+
+    // Process any incoming X events
+    TXWindow::handleXEvents(dpy);
+    
+    // Process expired timers and get the time until the next one
+    int timeoutMs = Timer::checkTimeouts();
+    if (timeoutMs) {
+      tv.tv_sec = timeoutMs / 1000;
+      tv.tv_usec = (timeoutMs % 1000) * 1000;
+      tvp = &tv;
+    }
+    
+    // If there are X requests pending then poll, don't wait!
+    if (XPending(dpy)) {
+      tv.tv_usec = tv.tv_sec = 0;
+      tvp = &tv;
+    }
+
+    // Wait for X events, VNC traffic, or the next timer expiry
+    FD_ZERO(&rfds);
+    FD_SET(ConnectionNumber(dpy), &rfds);
+    FD_SET(sock->getFd(), &rfds);
+    int n = select(FD_SETSIZE, &rfds, 0, 0, tvp);
+    if (n < 0) throw rdr::SystemException("select",errno);
+  } while (!(FD_ISSET(sock->getFd(), &rfds)));
+}
+
+
+// getPasswd() is called by the CSecurity object when it needs us to read a
+// password from the user.
+
+void CConn::getUserPasswd(char** user, char** password)
+{
+  CharArray passwordFileStr(passwordFile.getData());
+  if (!user && passwordFileStr.buf[0]) {
+    FILE* fp = fopen(passwordFileStr.buf, "r");
+    if (!fp) throw rfb::Exception("Opening password file failed");
+    ObfuscatedPasswd obfPwd(256);
+    obfPwd.length = fread(obfPwd.buf, 1, obfPwd.length, fp);
+    fclose(fp);
+    PlainPasswd passwd(obfPwd);
+    *password = passwd.takeBuf();
+    return;
+  }
+
+  const char* secType = secTypeName(getCurrentCSecurity()->getType());
+  const char* titlePrefix = _("VNC authentication");
+  unsigned int titleLen = strlen(titlePrefix) + strlen(secType) + 4;
+  CharArray title(titleLen);
+  snprintf(title.buf, titleLen, "%s [%s]", titlePrefix, secType);
+  PasswdDialog dlg(dpy, title.buf, !user);
+  if (!dlg.show()) throw rfb::Exception("Authentication cancelled");
+  if (user)
+    *user = strDup(dlg.userEntry.getText());
+  *password = strDup(dlg.passwdEntry.getText());
+}
+
+
+// CConnection callback methods
+
+// getCSecurity() gets the appropriate CSecurity object for the security
+// types which we support.
+CSecurity* CConn::getCSecurity(int secType) {
+  switch (secType) {
+  case secTypeNone:
+    return new CSecurityNone();
+  case secTypeVncAuth:
+    return new CSecurityVncAuth(this);
+  default:
+    throw rfb::Exception("Unsupported secType?");
+  }
+}
+
+// serverInit() is called when the serverInit message has been received.  At
+// this point we create the desktop window and display it.  We also tell the
+// server the pixel format and encodings to use and request the first update.
+void CConn::serverInit() {
+  CConnection::serverInit();
+
+  // If using AutoSelect with old servers, start in FullColor
+  // mode. See comment in autoSelectFormatAndEncoding. 
+  if (cp.beforeVersion(3, 8) && autoSelect) {
+    fullColour = true;
+  }
+
+  serverPF = cp.pf();
+  desktop = new DesktopWindow(dpy, cp.width, cp.height, serverPF, this);
+  desktopEventHandler = desktop->setEventHandler(this);
+  desktop->addEventMask(KeyPressMask | KeyReleaseMask);
+  fullColourPF = desktop->getPF();
+  if (!serverPF.trueColour)
+    fullColour = true;
+  recreateViewport();
+  formatChange = encodingChange = true;
+  requestNewUpdate();
+}
+
+// setDesktopSize() is called when the desktop size changes (including when
+// it is set initially).
+void CConn::setDesktopSize(int w, int h) {
+  CConnection::setDesktopSize(w,h);
+  if (desktop) {
+    desktop->resize(w, h);
+    recreateViewport();
+  }
+}
+
+// framebufferUpdateEnd() is called at the end of an update.
+// For each rectangle, the FdInStream will have timed the speed
+// of the connection, allowing us to select format and encoding
+// appropriately, and then request another incremental update.
+void CConn::framebufferUpdateEnd() {
+  if (debugDelay != 0) {
+    XSync(dpy, False);
+    struct timeval tv;
+    tv.tv_sec = debugDelay / 1000;
+    tv.tv_usec = (debugDelay % 1000) * 1000;
+    select(0, 0, 0, 0, &tv);
+    std::list<rfb::Rect>::iterator i;
+    for (i = debugRects.begin(); i != debugRects.end(); i++) {
+      desktop->invertRect(*i);
+    }
+    debugRects.clear();
+  }
+  desktop->framebufferUpdateEnd();
+  if (autoSelect)
+    autoSelectFormatAndEncoding();
+  requestNewUpdate();
+}
+
+// The rest of the callbacks are fairly self-explanatory...
+
+void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs)
+{
+  desktop->setColourMapEntries(firstColour, nColours, rgbs);
+}
+
+void CConn::bell() { XBell(dpy, 0); }
+
+void CConn::serverCutText(const char* str, int len) {
+  desktop->serverCutText(str,len);
+}
+
+// We start timing on beginRect and stop timing on endRect, to
+// avoid skewing the bandwidth estimation as a result of the server
+// being slow or the network having high latency
+void CConn::beginRect(const Rect& r, unsigned int encoding)
+{
+  sock->inStream().startTiming();
+  if (encoding != encodingCopyRect) {
+    lastServerEncoding = encoding;
+  }
+}
+
+void CConn::endRect(const Rect& r, unsigned int encoding)
+{
+  sock->inStream().stopTiming();
+  if (debugDelay != 0) {
+    desktop->invertRect(r);
+    debugRects.push_back(r);
+  }
+}
+
+void CConn::fillRect(const rfb::Rect& r, rfb::Pixel p) {
+  desktop->fillRect(r,p);
+}
+void CConn::imageRect(const rfb::Rect& r, void* p) {
+  desktop->imageRect(r,p);
+}
+void CConn::copyRect(const rfb::Rect& r, int sx, int sy) {
+  desktop->copyRect(r,sx,sy);
+}
+void CConn::setCursor(int width, int height, const Point& hotspot,
+                      void* data, void* mask) {
+  desktop->setCursor(width, height, hotspot, data, mask);
+}
+
+
+// Menu stuff - menuSelect() is called when the user selects a menu option.
+
+enum { ID_OPTIONS, ID_INFO, ID_FULLSCREEN, ID_REFRESH, ID_F8, ID_CTRLALTDEL,
+       ID_ABOUT, ID_DISMISS, ID_EXIT, ID_NEWCONN, ID_CTRL, ID_ALT };
+
+void CConn::initMenu() {
+  menuEventHandler = menu.setEventHandler(this);
+  menu.addEventMask(KeyPressMask | KeyReleaseMask);
+  menu.addEntry(_("Exit viewer"), ID_EXIT);
+  menu.addEntry(0, 0);
+  menu.addEntry(_("Full screen"), ID_FULLSCREEN);
+  menu.check(ID_FULLSCREEN, fullScreen);
+  menu.addEntry(0, 0);
+  menu.addEntry(_("Ctrl"), ID_CTRL);
+  menu.addEntry(_("Alt"), ID_ALT);
+  CharArray menuKeyStr(menuKey.getData());
+  CharArray sendMenuKey(64);
+  snprintf(sendMenuKey.buf, 64, _("Send %s"), menuKeyStr.buf);
+  menu.addEntry(sendMenuKey.buf, ID_F8);
+  menu.addEntry(_("Send Ctrl-Alt-Del"), ID_CTRLALTDEL);
+  menu.addEntry(0, 0);
+  menu.addEntry(_("Refresh screen"), ID_REFRESH);
+  menu.addEntry(0, 0);
+  menu.addEntry(_("New connection..."), ID_NEWCONN);
+  menu.addEntry(_("Options..."), ID_OPTIONS);
+  menu.addEntry(_("Connection info..."), ID_INFO);
+  menu.addEntry(_("About VNCviewer..."), ID_ABOUT);
+  menu.addEntry(0, 0);
+  menu.addEntry(_("Dismiss menu"), ID_DISMISS);
+  menu.toplevel(_("VNC Menu"), this);
+  menu.setBorderWidth(1);
+}
+
+void CConn::showMenu(int x, int y) {
+  menu.check(ID_FULLSCREEN, fullScreen);
+  if (x + menu.width() > viewport->width())
+      x = viewport->width() - menu.width();
+  if (y + menu.height() > viewport->height())
+      y = viewport->height() - menu.height();
+  menu.move(x, y);
+  menu.raise();
+  menu.map();
+}
+
+void CConn::menuSelect(long id, TXMenu* m) {
+  switch (id) {
+  case ID_NEWCONN:
+    {
+      menu.unmap();
+      if (fullScreen) {
+        fullScreen = false;
+        if (viewport) recreateViewport();
+      }
+      int pid = fork();
+      if (pid < 0) { perror("fork"); exit(1); }
+      if (pid == 0) {
+        delete sock;
+        close(ConnectionNumber(dpy));
+        struct timeval tv;
+        tv.tv_sec = 0;
+        tv.tv_usec = 200*1000;
+        select(0, 0, 0, 0, &tv);
+        execlp(programName, programName, 0);
+        perror("execlp"); exit(1);
+      }
+      break;
+    }
+  case ID_OPTIONS:
+    menu.unmap();
+    options.show();
+    break;
+  case ID_INFO:
+    {
+      menu.unmap();
+      char pfStr[100];
+      char spfStr[100];
+      cp.pf().print(pfStr, 100);
+      serverPF.print(spfStr, 100);
+      int secType = getCurrentCSecurity()->getType();
+      char infoText[1024];
+      snprintf(infoText, sizeof(infoText),
+	       _("Desktop name: %.80s\n"
+		 "Host: %.80s port: %d\n"
+		 "Size: %d x %d\n"
+		 "Pixel format: %s\n"
+		 "(server default %s)\n"
+		 "Requested encoding: %s\n"
+		 "Last used encoding: %s\n"
+		 "Line speed estimate: %d kbit/s\n"
+		 "Protocol version: %d.%d\n"
+		 "Security method: %s\n"),
+              cp.name(), serverHost, serverPort, cp.width, cp.height,
+              pfStr, spfStr, encodingName(currentEncoding),
+              encodingName(lastServerEncoding),
+              sock->inStream().kbitsPerSecond(),
+              cp.majorVersion, cp.minorVersion,
+              secTypeName(secType));
+      info.setText(infoText);
+      info.show();
+      break;
+    }
+  case ID_FULLSCREEN:
+    menu.unmap();
+    fullScreen = !fullScreen;
+    if (viewport) recreateViewport();
+    break;
+  case ID_REFRESH:
+    menu.unmap();
+    writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
+                                            false);
+    break;
+  case ID_F8:
+    menu.unmap();
+    if (!viewOnly) {
+      writer()->keyEvent(menuKeysym, true);
+      writer()->keyEvent(menuKeysym, false);
+    }
+    break;
+  case ID_CTRLALTDEL:
+    menu.unmap();
+    if (!viewOnly) {
+      writer()->keyEvent(XK_Control_L, true);
+      writer()->keyEvent(XK_Alt_L, true);
+      writer()->keyEvent(XK_Delete, true);
+      writer()->keyEvent(XK_Delete, false);
+      writer()->keyEvent(XK_Alt_L, false);
+      writer()->keyEvent(XK_Control_L, false);
+    }
+    break;
+  case ID_CTRL:
+    menu.unmap();
+    if (!viewOnly) {
+      ctrlDown = !ctrlDown;
+      writer()->keyEvent(XK_Control_L, ctrlDown);
+      menu.check(ID_CTRL, ctrlDown);
+    }
+    break;
+  case ID_ALT:
+    menu.unmap();
+    if (!viewOnly) {
+      altDown = !altDown;
+      writer()->keyEvent(XK_Alt_L, altDown);
+      menu.check(ID_ALT, altDown);
+    }
+    break;
+  case ID_ABOUT:
+    menu.unmap();
+    about.show();
+    break;
+  case ID_DISMISS:
+    menu.unmap();
+    break;
+  case ID_EXIT:
+    exit(1);
+    break;
+  }
+}
+
+
+// OptionsDialogCallback.  setOptions() sets the options dialog's checkboxes
+// etc to reflect our flags.  getOptions() sets our flags according to the
+// options dialog's checkboxes.
+
+void CConn::setOptions() {
+  char digit[2] = "0";
+  options.autoSelect.checked(autoSelect);
+  options.fullColour.checked(fullColour);
+  options.veryLowColour.checked(!fullColour && lowColourLevel == 0);
+  options.lowColour.checked(!fullColour && lowColourLevel == 1);
+  options.mediumColour.checked(!fullColour && lowColourLevel == 2);
+  options.tight.checked(currentEncoding == encodingTight);
+  options.zrle.checked(currentEncoding == encodingZRLE);
+  options.hextile.checked(currentEncoding == encodingHextile);
+  options.raw.checked(currentEncoding == encodingRaw);
+
+  options.customCompressLevel.checked(customCompressLevel); 
+  digit[0] = '0' + compressLevel;
+  options.compressLevel.setText(digit); 
+  options.noJpeg.checked(!noJpeg);
+  digit[0] = '0' + qualityLevel;
+  options.qualityLevel.setText(digit); 
+
+  options.viewOnly.checked(viewOnly);
+  options.acceptClipboard.checked(acceptClipboard);
+  options.sendClipboard.checked(sendClipboard);
+  options.sendPrimary.checked(sendPrimary);
+  if (state() == RFBSTATE_NORMAL)
+    options.shared.disabled(true);
+  else
+    options.shared.checked(shared);
+  options.fullScreen.checked(fullScreen);
+  options.useLocalCursor.checked(useLocalCursor);
+  options.dotWhenNoCursor.checked(dotWhenNoCursor);
+}
+
+void CConn::getOptions() {
+  autoSelect = options.autoSelect.checked();
+  if (fullColour != options.fullColour.checked())
+    formatChange = true;
+  fullColour = options.fullColour.checked();
+  if (!fullColour) {
+    int newLowColourLevel = (options.veryLowColour.checked() ? 0 :
+                             options.lowColour.checked() ? 1 : 2);
+    if (newLowColourLevel != lowColourLevel) {
+      lowColourLevel.setParam(newLowColourLevel);
+      formatChange = true;
+    }
+  }
+  unsigned int newEncoding = (options.tight.checked() ? encodingTight :
+			      options.zrle.checked() ? encodingZRLE :
+                              options.hextile.checked() ? encodingHextile :
+                              encodingRaw);
+  if (newEncoding != currentEncoding) {
+    currentEncoding = newEncoding;
+    encodingChange = true;
+  }
+
+  customCompressLevel.setParam(options.customCompressLevel.checked());
+  if (cp.customCompressLevel != customCompressLevel) {
+    cp.customCompressLevel = customCompressLevel;
+    encodingChange = true;
+  }
+  compressLevel.setParam(options.compressLevel.getText());
+  if (cp.compressLevel != compressLevel) {
+    cp.compressLevel = compressLevel;
+    encodingChange = true;
+  }
+  noJpeg.setParam(!options.noJpeg.checked());
+  if (cp.noJpeg != noJpeg) {
+    cp.noJpeg = noJpeg;
+    encodingChange = true;
+  }
+  qualityLevel.setParam(options.qualityLevel.getText());
+  if (cp.qualityLevel != qualityLevel) {
+    cp.qualityLevel = qualityLevel;
+    encodingChange = true;
+  }
+
+  viewOnly.setParam(options.viewOnly.checked());
+  acceptClipboard.setParam(options.acceptClipboard.checked());
+  sendClipboard.setParam(options.sendClipboard.checked());
+  sendPrimary.setParam(options.sendPrimary.checked());
+  shared = options.shared.checked();
+  setShared(shared);
+  if (fullScreen != options.fullScreen.checked()) {
+    fullScreen = options.fullScreen.checked();
+    if (viewport) recreateViewport();
+  }
+  useLocalCursor.setParam(options.useLocalCursor.checked());
+  if (cp.supportsLocalCursor != useLocalCursor) {
+    cp.supportsLocalCursor = useLocalCursor;
+    encodingChange = true;
+    if (desktop)
+      desktop->resetLocalCursor();
+  }
+  dotWhenNoCursor.setParam(options.dotWhenNoCursor.checked());
+  checkEncodings();
+}
+
+void CConn::recreateViewport()
+{
+  TXViewport* oldViewport = viewport;
+  viewport = new TXViewport(dpy, cp.width, cp.height);
+  desktop->setViewport(viewport);
+  CharArray windowNameStr(windowName.getData());
+  if (!windowNameStr.buf[0]) {
+    windowNameStr.replaceBuf(new char[256]);
+    snprintf(windowNameStr.buf, 256, "VNC: %.240s", cp.name());
+  }
+  viewport->toplevel(windowNameStr.buf, this, argc, argv);
+  viewport->setBumpScroll(fullScreen);
+  XSetWindowAttributes attr;
+  attr.override_redirect = fullScreen;
+  XChangeWindowAttributes(dpy, viewport->win(), CWOverrideRedirect, &attr);
+  XChangeWindowAttributes(dpy, menu.win(), CWOverrideRedirect, &attr);
+  XChangeWindowAttributes(dpy, options.win(), CWOverrideRedirect, &attr);
+  XChangeWindowAttributes(dpy, about.win(), CWOverrideRedirect, &attr);
+  XChangeWindowAttributes(dpy, info.win(), CWOverrideRedirect, &attr);
+  reconfigureViewport();
+  menu.setTransientFor(viewport->win());
+  viewport->map();
+  if (fullScreen) {
+    XGrabKeyboard(dpy, desktop->win(), True, GrabModeAsync, GrabModeAsync,
+                  CurrentTime);
+  } else {
+    XUngrabKeyboard(dpy, CurrentTime);
+  }
+  if (oldViewport) delete oldViewport;
+}
+
+void CConn::reconfigureViewport()
+{
+  viewport->setMaxSize(cp.width, cp.height);
+  if (fullScreen) {
+    viewport->resize(DisplayWidth(dpy,DefaultScreen(dpy)),
+                     DisplayHeight(dpy,DefaultScreen(dpy)));
+  } else {
+    int w = cp.width;
+    int h = cp.height;
+    if (w + wmDecorationWidth >= DisplayWidth(dpy,DefaultScreen(dpy)))
+      w = DisplayWidth(dpy,DefaultScreen(dpy)) - wmDecorationWidth;
+    if (h + wmDecorationHeight >= DisplayHeight(dpy,DefaultScreen(dpy)))
+      h = DisplayHeight(dpy,DefaultScreen(dpy)) - wmDecorationHeight;
+
+    int x = (DisplayWidth(dpy,DefaultScreen(dpy)) - w - wmDecorationWidth) / 2;
+    int y = (DisplayHeight(dpy,DefaultScreen(dpy)) - h - wmDecorationHeight)/2;
+
+    CharArray geometryStr(geometry.getData());
+    viewport->setGeometry(geometryStr.buf, x, y, w, h);
+  }
+}
+
+// Note: The method below is duplicated in vncviewer/cview.cxx!
+
+// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
+// to the connection speed:
+//
+//   Above 16Mbps (timing for at least a second), switch to hextile
+//   Otherwise, switch to ZRLE
+//
+//   Above 256Kbps, use full colour mode
+//
+void CConn::autoSelectFormatAndEncoding()
+{
+  int kbitsPerSecond = sock->inStream().kbitsPerSecond();
+  unsigned int newEncoding = currentEncoding;
+  bool newFullColour = fullColour;
+  unsigned int timeWaited = sock->inStream().timeWaited();
+
+  // Select best encoding
+  if (kbitsPerSecond > 16000 && timeWaited >= 10000) {
+    newEncoding = encodingHextile;
+  } else {
+    newEncoding = encodingZRLE;
+  }
+
+  if (newEncoding != currentEncoding) {
+    vlog.info("Throughput %d kbit/s - changing to %s encoding",
+              kbitsPerSecond, encodingName(newEncoding));
+    currentEncoding = newEncoding;
+    encodingChange = true;
+  }
+
+  if (kbitsPerSecond == 0) {
+    return;
+  }
+
+  if (cp.beforeVersion(3, 8)) {
+    // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
+    // cursors "asynchronously". If this happens in the middle of a
+    // pixel format change, the server will encode the cursor with
+    // the old format, but the client will try to decode it
+    // according to the new format. This will lead to a
+    // crash. Therefore, we do not allow automatic format change for
+    // old servers.
+    return;
+  }
+  
+  // Select best color level
+  newFullColour = (kbitsPerSecond > 256);
+  if (newFullColour != fullColour) {
+    vlog.info("Throughput %d kbit/s - full color is now %s", 
+	      kbitsPerSecond,
+	      newFullColour ? "enabled" : "disabled");
+    fullColour = newFullColour;
+    formatChange = true;
+  } 
+}
+
+// checkEncodings() sends a setEncodings message if one is needed.
+void CConn::checkEncodings()
+{
+  if (encodingChange && writer()) {
+    vlog.info("Using %s encoding",encodingName(currentEncoding));
+    writer()->writeSetEncodings(currentEncoding, true);
+    encodingChange = false;
+  }
+}
+
+// requestNewUpdate() requests an update from the server, having set the
+// format and encoding appropriately.
+void CConn::requestNewUpdate()
+{
+  if (formatChange) {
+    if (fullColour) {
+      desktop->setPF(fullColourPF);
+    } else {
+      if (lowColourLevel == 0)
+        desktop->setPF(PixelFormat(8,3,0,1,1,1,1,2,1,0));
+      else if (lowColourLevel == 1)
+        desktop->setPF(PixelFormat(8,6,0,1,3,3,3,4,2,0));
+      else
+        desktop->setPF(PixelFormat(8,8,0,0));
+    }
+    char str[256];
+    desktop->getPF().print(str, 256);
+    vlog.info("Using pixel format %s",str);
+    cp.setPF(desktop->getPF());
+    writer()->writeSetPixelFormat(cp.pf());
+  }
+  checkEncodings();
+  writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
+                                          !formatChange);
+  formatChange = false;
+}
diff --git a/unix/vncviewer/CConn.h b/unix/vncviewer/CConn.h
new file mode 100644
index 0000000..a81af48
--- /dev/null
+++ b/unix/vncviewer/CConn.h
@@ -0,0 +1,130 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+//
+// CConn represents a client connection to a VNC server.
+//
+
+#ifndef __CCONN_H__
+#define __CCONN_H__
+
+#include <rfb/CConnection.h>
+#include <rfb/Exception.h>
+#include <rfb/UserPasswdGetter.h>
+#include <rdr/FdInStream.h>
+#include <list>
+
+#include "TXWindow.h"
+#include "AboutDialog.h"
+#include "InfoDialog.h"
+#include "TXMenu.h"
+#include "OptionsDialog.h"
+
+class TXWindow;
+class TXViewport;
+class DesktopWindow;
+namespace network { class Socket; }
+
+class CConn : public rfb::CConnection, public rfb::UserPasswdGetter,
+              public TXDeleteWindowCallback,
+              public rdr::FdInStreamBlockCallback,
+              public TXMenuCallback , public OptionsDialogCallback,
+              public TXEventHandler
+{
+public:
+
+  CConn(Display* dpy_, int argc_, char** argv_, network::Socket* sock_,
+        char* vncServerName, bool reverse=false);
+  ~CConn();
+
+  // TXDeleteWindowCallback methods
+  void deleteWindow(TXWindow* w);
+
+  // FdInStreamBlockCallback methods
+  void blockCallback();
+
+  // UserPasswdGetter methods
+  virtual void getUserPasswd(char** user, char** password);
+
+  // TXMenuCallback methods
+  void menuSelect(long id, TXMenu* m);
+
+  // OptionsDialogCallback methods
+  virtual void setOptions();
+  virtual void getOptions();
+
+  // TXEventHandler callback method
+  virtual void handleEvent(TXWindow* w, XEvent* ev);
+  
+  // CConnection callback methods
+  rfb::CSecurity* getCSecurity(int secType);
+  void serverInit();
+  void setDesktopSize(int w, int h);
+  void setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs);
+  void bell();
+  void serverCutText(const char* str, int len);
+  void framebufferUpdateEnd();
+  void beginRect(const rfb::Rect& r, unsigned int encoding);
+  void endRect(const rfb::Rect& r, unsigned int encoding);
+  void fillRect(const rfb::Rect& r, rfb::Pixel p);
+  void imageRect(const rfb::Rect& r, void* p);
+  void copyRect(const rfb::Rect& r, int sx, int sy);
+  void setCursor(int width, int height, const rfb::Point& hotspot,
+                 void* data, void* mask);
+
+private:
+
+  void recreateViewport();
+  void reconfigureViewport();
+  void initMenu();
+  void showMenu(int x, int y);
+  void autoSelectFormatAndEncoding();
+  void checkEncodings();
+  void requestNewUpdate();
+
+  Display* dpy;
+  int argc;
+  char** argv;
+  char* serverHost;
+  int serverPort;
+  network::Socket* sock;
+  rfb::PixelFormat serverPF;
+  TXViewport* viewport;
+  DesktopWindow* desktop;
+  TXEventHandler* desktopEventHandler;
+  rfb::PixelFormat fullColourPF;
+  std::list<rfb::Rect> debugRects;
+  unsigned int currentEncoding, lastServerEncoding;
+  bool fullColour;
+  bool autoSelect;
+  bool shared;
+  bool formatChange;
+  bool encodingChange;
+  bool sameMachine;
+  bool fullScreen;
+  bool ctrlDown;
+  bool altDown;
+  KeySym menuKeysym;
+  TXMenu menu;
+  TXEventHandler* menuEventHandler;
+  OptionsDialog options;
+  AboutDialog about;
+  InfoDialog info;
+  bool reverseConnection;
+};
+
+#endif
diff --git a/unix/vncviewer/DesktopWindow.cxx b/unix/vncviewer/DesktopWindow.cxx
new file mode 100644
index 0000000..657572a
--- /dev/null
+++ b/unix/vncviewer/DesktopWindow.cxx
@@ -0,0 +1,571 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+//
+// DesktopWindow.cxx
+//
+
+#include "DesktopWindow.h"
+#include "CConn.h"
+#include <rfb/CMsgWriter.h>
+#include <rfb/LogWriter.h>
+#include <X11/keysym.h>
+#include <X11/Xatom.h>
+#include <stdio.h>
+#include <string.h>
+#include "parameters.h"
+
+#ifndef XK_ISO_Left_Tab
+#define	XK_ISO_Left_Tab					0xFE20
+#endif
+
+static rdr::U8 reverseBits[] = {
+  0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0,
+  0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+  0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4,
+  0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+  0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc,
+  0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+  0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca,
+  0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+  0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6,
+  0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+  0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1,
+  0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+  0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9,
+  0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+  0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd,
+  0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+  0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3,
+  0x33, 0xb3, 0x73, 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+  0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7,
+  0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+  0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf,
+  0x3f, 0xbf, 0x7f, 0xff
+};
+
+using namespace rfb;
+
+static rfb::LogWriter vlog("DesktopWindow");
+
+DesktopWindow::DesktopWindow(Display* dpy, int w, int h,
+                             const rfb::PixelFormat& serverPF,
+                             CConn* cc_, TXWindow* parent)
+  : TXWindow(dpy, w, h, parent), cc(cc_), im(0),
+    cursorVisible(false), cursorAvailable(false), currentSelectionTime(0),
+    newSelection(0), gettingInitialSelectionTime(true),
+    newServerCutText(false), serverCutText_(0),
+    setColourMapEntriesTimer(this), viewport(0),
+    pointerEventTimer(this),
+    lastButtonMask(0)
+{
+  setEventHandler(this);
+  gc = XCreateGC(dpy, win(), 0, 0);
+  addEventMask(ExposureMask | ButtonPressMask | ButtonReleaseMask |
+               PointerMotionMask | KeyPressMask | KeyReleaseMask |
+               EnterWindowMask | LeaveWindowMask);
+  createXCursors();
+  XDefineCursor(dpy, win(), dotCursor);
+  im = new TXImage(dpy, width(), height());
+  if (!serverPF.trueColour)
+    im->setPF(serverPF);
+  XConvertSelection(dpy, sendPrimary ? XA_PRIMARY : xaCLIPBOARD, xaTIMESTAMP,
+                    xaSELECTION_TIME, win(), CurrentTime);
+  memset(downKeysym, 0, 256*4);
+}
+
+DesktopWindow::~DesktopWindow()
+{
+  XFreeGC(dpy, gc);
+  XFreeCursor(dpy, dotCursor);
+  XFreeCursor(dpy, noCursor);
+  if (localXCursor)
+    XFreeCursor(dpy, localXCursor);
+  delete im;
+}
+
+void DesktopWindow::setViewport(TXViewport* viewport_)
+{
+  viewport = viewport_;
+  viewport->setChild(this);
+}
+
+// Cursor stuff
+
+void DesktopWindow::createXCursors()
+{
+  static char dotSource[] = { 0x00, 0x0e, 0x0e, 0x0e, 0x00 };
+  static char dotMask[]   = { 0x1f, 0x1f, 0x1f, 0x1f, 0x1f };
+  Pixmap source = XCreateBitmapFromData(dpy, win(), dotSource, 5, 5);
+  Pixmap mask = XCreateBitmapFromData(dpy, win(), dotMask, 5, 5);
+  XColor fg, bg;
+  fg.red = fg.green = fg.blue = 0;
+  bg.red = bg.green = bg.blue = 0xffff;
+  dotCursor = XCreatePixmapCursor(dpy, source, mask, &fg, &bg, 2, 2);
+  XFreePixmap(dpy, source);
+  XFreePixmap(dpy, mask);
+  char zero = 0;
+  Pixmap empty = XCreateBitmapFromData(dpy, win(), &zero, 1, 1);
+  noCursor = XCreatePixmapCursor(dpy, empty, empty, &fg, &bg, 0, 0);
+  XFreePixmap(dpy, empty);
+  localXCursor = 0;
+}
+
+void DesktopWindow::setCursor(int width, int height, const Point& hotspot,
+                              void* data, void* mask)
+{
+  if (!useLocalCursor) return;
+
+  hideLocalCursor();
+
+  int mask_len = ((width+7)/8) * height;
+
+  int i;
+  for (i = 0; i < mask_len; i++)
+    if (((rdr::U8*)mask)[i]) break;
+
+  if (i == mask_len) {
+    if (dotWhenNoCursor) {
+      vlog.debug("cursor is empty - using dot");
+      XDefineCursor(dpy, win(), dotCursor);
+    } else {
+      XDefineCursor(dpy, win(), noCursor);
+    }
+    cursorAvailable = false;
+    return;
+  }
+
+  cursor.hotspot = hotspot;
+
+  cursor.setSize(width, height);
+  cursor.setPF(getPF());
+  cursor.imageRect(cursor.getRect(), data);
+
+  cursorBacking.setSize(width, height);
+  cursorBacking.setPF(getPF());
+
+  delete [] cursor.mask.buf;
+  cursor.mask.buf = new rdr::U8[mask_len];
+  memcpy(cursor.mask.buf, mask, mask_len);
+
+  Pixel pix0, pix1;
+  rdr::U8Array bitmap(cursor.getBitmap(&pix0, &pix1));
+  if (bitmap.buf && cursor.getRect().contains(cursor.hotspot)) {
+    int bytesPerRow = (cursor.width() + 7) / 8;
+    for (int j = 0; j < cursor.height(); j++) {
+      for (int i = 0; i < bytesPerRow; i++) {
+        bitmap.buf[j * bytesPerRow + i]
+          = reverseBits[bitmap.buf[j * bytesPerRow + i]];
+        cursor.mask.buf[j * bytesPerRow + i]
+          = reverseBits[cursor.mask.buf[j * bytesPerRow + i]];
+      }
+    }
+    Pixmap source = XCreateBitmapFromData(dpy, win(), (char*)bitmap.buf,
+                                          cursor.width(), cursor.height());
+    Pixmap mask = XCreateBitmapFromData(dpy, win(), (char*)cursor.mask.buf,
+                                        cursor.width(), cursor.height());
+    Colour rgb;
+    XColor fg, bg;
+    getPF().rgbFromPixel(pix1, im->getColourMap(), &rgb);
+    fg.red = rgb.r; fg.green = rgb.g; fg.blue = rgb.b;
+    getPF().rgbFromPixel(pix0, im->getColourMap(), &rgb);
+    bg.red = rgb.r; bg.green = rgb.g; bg.blue = rgb.b;
+    if (localXCursor)
+      XFreeCursor(dpy, localXCursor);
+    localXCursor = XCreatePixmapCursor(dpy, source, mask, &fg, &bg,
+                                       cursor.hotspot.x, cursor.hotspot.y);
+    XDefineCursor(dpy, win(), localXCursor);
+    XFreePixmap(dpy, source);
+    XFreePixmap(dpy, mask);
+    cursorAvailable = false;
+    return;
+  }
+
+  if (!cursorAvailable) {
+    XDefineCursor(dpy, win(), noCursor);
+    cursorAvailable = true;
+  }
+
+  showLocalCursor();
+}
+
+void DesktopWindow::resetLocalCursor()
+{
+  hideLocalCursor();
+  XDefineCursor(dpy, win(), dotCursor);
+  cursorAvailable = false;
+}
+
+void DesktopWindow::hideLocalCursor()
+{
+  // - Blit the cursor backing store over the cursor
+  if (cursorVisible) {
+    cursorVisible = false;
+    im->imageRect(cursorBackingRect, cursorBacking.data);
+    im->put(win(), gc, cursorBackingRect);
+  }
+}
+
+void DesktopWindow::showLocalCursor()
+{
+  if (cursorAvailable && !cursorVisible) {
+    if (!getPF().equal(cursor.getPF()) ||
+        cursor.getRect().is_empty()) {
+      vlog.error("attempting to render invalid local cursor");
+      XDefineCursor(dpy, win(), dotCursor);
+      cursorAvailable = false;
+      return;
+    }
+    cursorVisible = true;
+
+    rfb::Rect cursorRect = (cursor.getRect().translate(cursorPos).
+                            translate(cursor.hotspot.negate()));
+    cursorBackingRect = cursorRect.intersect(im->getRect());
+    im->getImage(cursorBacking.data, cursorBackingRect);
+
+    im->maskRect(cursorRect, cursor.data, cursor.mask.buf);
+    im->put(win(), gc, cursorBackingRect);
+  }
+}
+
+// setColourMapEntries() changes some of the entries in the colourmap.
+// Unfortunately these messages are often sent one at a time, so we delay the
+// settings taking effect by 100ms.  This is because recalculating the internal
+// translation table can be expensive.
+void DesktopWindow::setColourMapEntries(int firstColour, int nColours,
+                                        rdr::U16* rgbs)
+{
+  im->setColourMapEntries(firstColour, nColours, rgbs);
+  if (!setColourMapEntriesTimer.isStarted())
+    setColourMapEntriesTimer.start(100);
+}
+
+void DesktopWindow::serverCutText(const char* str, int len)
+{
+  if (acceptClipboard) {
+    newServerCutText = true;
+    delete [] serverCutText_;
+    serverCutText_ = new char[len+1];
+    memcpy(serverCutText_, str, len);
+    serverCutText_[len] = 0;
+  }
+}
+
+
+// Call XSync() at the end of an update.  We do this because we'd like to
+// ensure that the current update has actually been drawn by the X server
+// before the next update arrives - this is necessary for copyRect to
+// behave correctly.  In particular, if part of the source of a copyRect is
+// not actually displayed in the window, then XCopyArea results in
+// GraphicsExpose events, which require us to draw from the off-screen
+// image.  By the time XSync returns, the GraphicsExpose events will be in
+// Xlib's queue, so hopefully will be processed before the next update.
+// Possibly we should process the GraphicsExpose events here explicitly?
+
+void DesktopWindow::framebufferUpdateEnd()
+{
+  XSync(dpy, False);
+}
+
+
+// invertRect() flips all the bits in every pixel in the given rectangle
+
+void DesktopWindow::invertRect(const Rect& r)
+{
+  int stride;
+  rdr::U8* p = im->getPixelsRW(r, &stride);
+  for (int y = 0; y < r.height(); y++) {
+    for (int x = 0; x < r.width(); x++) {
+      switch (getPF().bpp) {
+      case 8:  ((rdr::U8* )p)[x+y*stride] ^= 0xff;       break;
+      case 16: ((rdr::U16*)p)[x+y*stride] ^= 0xffff;     break;
+      case 32: ((rdr::U32*)p)[x+y*stride] ^= 0xffffffff; break;
+      }
+    }
+  }
+  im->put(win(), gc, r);
+}
+
+
+// resize() - resize the window and the image, taking care to remove the local
+// cursor first.
+void DesktopWindow::resize(int w, int h)
+{
+  hideLocalCursor();
+  TXWindow::resize(w, h);
+  im->resize(w, h);
+}
+
+
+bool DesktopWindow::handleTimeout(rfb::Timer* timer)
+{
+  if (timer == &setColourMapEntriesTimer) {
+    im->updateColourMap();
+    im->put(win(), gc, im->getRect());
+  } else if (timer == &pointerEventTimer) {
+    if (!viewOnly) {
+      cc->writer()->pointerEvent(lastPointerPos, lastButtonMask);
+    }
+  }
+  return false;
+}
+
+
+void DesktopWindow::handlePointerEvent(const Point& pos, int buttonMask)
+{
+  if (!viewOnly) {
+    if (pointerEventInterval == 0 || buttonMask != lastButtonMask) {
+      cc->writer()->pointerEvent(pos, buttonMask);
+    } else {
+      if (!pointerEventTimer.isStarted())
+        pointerEventTimer.start(pointerEventInterval);
+    }
+    lastPointerPos = pos;
+    lastButtonMask = buttonMask;
+  }
+  // - If local cursor rendering is enabled then use it
+  if (cursorAvailable) {
+    // - Render the cursor!
+    if (!pos.equals(cursorPos)) {
+      hideLocalCursor();
+      if (im->getRect().contains(pos)) {
+        cursorPos = pos;
+        showLocalCursor();
+      }
+    }
+  }
+}
+
+
+// handleXEvent() handles the various X events on the window
+void DesktopWindow::handleEvent(TXWindow* w, XEvent* ev)
+{
+  switch (ev->type) {
+  case GraphicsExpose:
+  case Expose:
+    im->put(win(), gc, Rect(ev->xexpose.x, ev->xexpose.y,
+                            ev->xexpose.x + ev->xexpose.width,
+                            ev->xexpose.y + ev->xexpose.height));
+    break;
+
+  case MotionNotify:
+    while (XCheckTypedWindowEvent(dpy, win(), MotionNotify, ev));
+    if (viewport && viewport->bumpScrollEvent(&ev->xmotion)) break;
+    handlePointerEvent(Point(ev->xmotion.x, ev->xmotion.y),
+                       (ev->xmotion.state & 0x1f00) >> 8);
+    break;
+
+  case ButtonPress:
+    handlePointerEvent(Point(ev->xbutton.x, ev->xbutton.y),
+                       (((ev->xbutton.state & 0x1f00) >> 8) |
+                        (1 << (ev->xbutton.button-1))));
+    break;
+
+  case ButtonRelease:
+    handlePointerEvent(Point(ev->xbutton.x, ev->xbutton.y),
+                       (((ev->xbutton.state & 0x1f00) >> 8) &
+                        ~(1 << (ev->xbutton.button-1))));
+    break;
+
+  case KeyPress:
+    if (!viewOnly) {
+      KeySym ks;
+      char keyname[256];
+      XLookupString(&ev->xkey, keyname, 256, &ks, NULL);
+      bool fakeShiftPress = false;
+
+      // Turn ISO_Left_Tab into shifted Tab
+      if (ks == XK_ISO_Left_Tab) {
+        fakeShiftPress = !(ev->xkey.state & ShiftMask);
+        ks = XK_Tab;
+      }
+
+      if (fakeShiftPress)
+        cc->writer()->keyEvent(XK_Shift_L, true);
+
+      downKeysym[ev->xkey.keycode] = ks;
+      cc->writer()->keyEvent(ks, true);
+
+      if (fakeShiftPress)
+        cc->writer()->keyEvent(XK_Shift_L, false);
+      break;
+    }
+
+  case KeyRelease:
+    if (!viewOnly) {
+      if (downKeysym[ev->xkey.keycode]) {
+        cc->writer()->keyEvent(downKeysym[ev->xkey.keycode], false);
+        downKeysym[ev->xkey.keycode] = 0;
+      }
+    }
+    break;
+
+  case EnterNotify:
+    newSelection = 0;
+    if (sendPrimary && !selectionOwner(XA_PRIMARY)) {
+      XConvertSelection(dpy, XA_PRIMARY, xaTIMESTAMP, xaSELECTION_TIME,
+                        win(), ev->xcrossing.time);
+    } else if (!selectionOwner(xaCLIPBOARD)) {
+      XConvertSelection(dpy, xaCLIPBOARD, xaTIMESTAMP, xaSELECTION_TIME,
+                        win(), ev->xcrossing.time);
+    }
+    break;
+
+  case LeaveNotify:
+    if (serverCutText_ && newServerCutText) {
+      newServerCutText = false;
+      vlog.debug("acquiring primary and clipboard selections");
+      XStoreBytes(dpy, serverCutText_, strlen(serverCutText_));
+      ownSelection(XA_PRIMARY, ev->xcrossing.time);
+      ownSelection(xaCLIPBOARD, ev->xcrossing.time);
+      currentSelectionTime = ev->xcrossing.time;
+    }
+    // Release all keys - this should probably done on a FocusOut event, but
+    // LeaveNotify is near enough...
+    for (int i = 8; i < 256; i++) {
+      if (downKeysym[i]) {
+        cc->writer()->keyEvent(downKeysym[i], false);
+        downKeysym[i] = 0;
+      }
+    }
+    break;
+  }
+}
+
+// selectionRequest() is called when we are the selection owner and another X
+// client has requested the selection.  We simply put the server's cut text
+// into the requested property.  TXWindow will handle the rest.
+bool DesktopWindow::selectionRequest(Window requestor,
+                                     Atom selection, Atom property)
+{
+  XChangeProperty(dpy, requestor, property, XA_STRING, 8,
+                  PropModeReplace, (unsigned char*)serverCutText_,
+                  strlen(serverCutText_));
+  return true;
+}
+
+
+// selectionNotify() is called when we have requested any information about a
+// selection from the selection owner.  Note that there are two selections,
+// PRIMARY and CLIPBOARD, plus the cut buffer, and we try to use whichever is
+// the most recent of the three.
+//
+// There are two different "targets" for which selectionNotify() is called, the
+// timestamp and the actual string value of the selection.  We always use the
+// timestamp to decide which selection to retrieve.
+//
+// The first time selectionNotify() is called is when we are trying to find the
+// timestamp of the selections at initialisation.  This should be called first
+// for PRIMARY, then we call XConvertSelection() for CLIPBOARD.  The second
+// time should be the result for CLIPBOARD.  At this stage we've got the
+// "currentSelectionTime" so we return.
+//
+// Subsequently selectionNotify() is called whenever the mouse enters the
+// viewer window.  Again, the first time it is called should be the timestamp
+// for PRIMARY, and we then request the timestamp for CLIPBOARD.  When
+// selectionNotify() is called again with the timestamp for CLIPBOARD, we now
+// know if either selection is "new" i.e. later than the previous value of
+// currentSelectionTime.  The last thing to check is the timestamp on the cut
+// buffer.  If the cut buffer is newest we send that to the server, otherwise
+// if one of the selections was newer, we request the string value of that
+// selection.
+//
+// Finally, if we get selectionNotify() called for the string value of a
+// selection, we sent that to the server.
+//
+// As a final minor complication, when one of the selections is actually owned
+// by us, we don't request the details for it.
+
+// TIME_LATER treats 0 as meaning a long time ago, so a==0 means a cannot be
+// later than b.  This is different to the usual meaning of CurrentTime.
+#define TIME_LATER(a, b) ((a) != 0 && ((b) == 0 || (long)((a) - (b)) > 0))
+
+
+void DesktopWindow::selectionNotify(XSelectionEvent* ev, Atom type, int format,
+                                    int nitems, void* data)
+{
+  if (ev->requestor != win())
+    return;
+
+  if (ev->target == xaTIMESTAMP) {
+    if (ev->property == xaSELECTION_TIME) {
+      if (data && format == 32 && nitems == 1) {
+        Time t = *(rdr::U32 *)data;
+        vlog.debug("selection (%d) time is %d, later %d",
+                   ev->selection, t, TIME_LATER(t, currentSelectionTime));
+        if (TIME_LATER(t, currentSelectionTime)) {
+          currentSelectionTime = t;
+          newSelection = ev->selection;
+        }
+      }
+    } else {
+      vlog.debug("no selection (%d)",ev->selection);
+    }
+
+    if (ev->selection == XA_PRIMARY) {
+      if (!selectionOwner(xaCLIPBOARD)) {
+        XConvertSelection(dpy, xaCLIPBOARD, xaTIMESTAMP, xaSELECTION_TIME,
+                          win(), ev->time);
+        return;
+      }
+    } else if (ev->selection != xaCLIPBOARD) {
+      vlog.error("unknown selection %d",ev->selection);
+      return;
+    }
+
+    if (gettingInitialSelectionTime) {
+      gettingInitialSelectionTime = false;
+      return;
+    }
+
+    if (!sendClipboard) return;
+    if (sendPrimary) {
+      vlog.debug("cut buffer time is %d, later %d", cutBufferTime,
+                 TIME_LATER(cutBufferTime, currentSelectionTime));
+      if (TIME_LATER(cutBufferTime, currentSelectionTime)) {
+        currentSelectionTime = cutBufferTime;
+        int len;
+        char* str = XFetchBytes(dpy, &len);
+        if (str) {
+          if (!viewOnly) {
+            vlog.debug("sending cut buffer to server");
+            cc->writer()->clientCutText(str, len);
+          }
+          XFree(str);
+          return;
+        }
+      }
+    }
+    if (newSelection) {
+      XConvertSelection(dpy, newSelection, XA_STRING, xaSELECTION_STRING,
+                        win(), CurrentTime);
+    }
+
+  } else if (ev->target == XA_STRING) {
+    if (!sendClipboard) return;
+    if (ev->property == xaSELECTION_STRING) {
+      if (data && format == 8) {
+        if (!viewOnly) {
+          vlog.debug("sending %s selection to server",
+                     ev->selection == XA_PRIMARY ? "primary" :
+                     ev->selection == xaCLIPBOARD ? "clipboard" : "unknown" );
+          cc->writer()->clientCutText((char*)data, nitems);
+        }
+      }
+    }
+  }
+}
diff --git a/unix/vncviewer/DesktopWindow.h b/unix/vncviewer/DesktopWindow.h
new file mode 100644
index 0000000..a1af750
--- /dev/null
+++ b/unix/vncviewer/DesktopWindow.h
@@ -0,0 +1,129 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+//
+// DesktopWindow is a TXWindow representing a VNC desktop.
+//
+
+#ifndef __DESKTOPWINDOW_H__
+#define __DESKTOPWINDOW_H__
+
+#include <rfb/Cursor.h>
+#include <rfb/Rect.h>
+#include <rfb/Timer.h>
+#include "TXWindow.h"
+#include "TXViewport.h"
+#include "TXImage.h"
+
+class CConn;
+
+class DesktopWindow : public TXWindow, public TXEventHandler,
+                      public rfb::Timer::Callback {
+public:
+
+  DesktopWindow(Display* dpy, int w, int h,
+                const rfb::PixelFormat& serverPF, CConn* cc_,
+                TXWindow* parent=0);
+  ~DesktopWindow();
+
+  void setViewport(TXViewport* viewport);
+
+  // getPF() and setPF() get and set the TXImage's pixel format
+  const rfb::PixelFormat& getPF() { return im->getPF(); }
+  void setPF(const rfb::PixelFormat& pf) { im->setPF(pf); }
+
+  // setCursor() sets the shape of the local cursor
+  void setCursor(int width, int height, const rfb::Point& hotspot,
+                 void* data, void* mask);
+
+  // resetLocalCursor() stops the rendering of the local cursor
+  void resetLocalCursor();
+
+  // Methods forwarded from CConn
+  void setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs);
+  void serverCutText(const char* str, int len);
+  void framebufferUpdateEnd();
+
+  void fillRect(const rfb::Rect& r, rfb::Pixel pix) {
+    if (r.overlaps(cursorBackingRect)) hideLocalCursor();
+    im->fillRect(r, pix);
+    im->put(win(), gc, r);
+    showLocalCursor();
+  }
+  void imageRect(const rfb::Rect& r, void* pixels) {
+    if (r.overlaps(cursorBackingRect)) hideLocalCursor();
+    im->imageRect(r, pixels);
+    im->put(win(), gc, r);
+    showLocalCursor();
+  }
+  void copyRect(const rfb::Rect& r, int srcX, int srcY) {
+    if (r.overlaps(cursorBackingRect) ||
+        cursorBackingRect.overlaps(rfb::Rect(srcX, srcY,
+                                             srcX+r.width(), srcY+r.height())))
+      hideLocalCursor();
+    if (im->usingShm())
+      XSync(dpy, False);
+    im->copyRect(r, rfb::Point(r.tl.x-srcX, r.tl.y-srcY));
+    XCopyArea(dpy, win(), win(), gc, srcX, srcY,
+              r.width(), r.height(), r.tl.x, r.tl.y);
+    showLocalCursor();
+  }
+  void invertRect(const rfb::Rect& r);
+
+  // TXWindow methods
+  virtual void resize(int w, int h);
+  virtual bool selectionRequest(Window requestor,
+                                Atom selection, Atom property);
+  virtual void selectionNotify(XSelectionEvent* ev, Atom type, int format,
+                               int nitems, void* data);
+  virtual void handleEvent(TXWindow* w, XEvent* ev);
+
+private:
+
+  void createXCursors();
+  void hideLocalCursor();
+  void showLocalCursor();
+  bool handleTimeout(rfb::Timer* timer);
+  void handlePointerEvent(const rfb::Point& pos, int buttonMask);
+
+  CConn* cc;
+  TXImage* im;
+  GC gc;
+  ::Cursor dotCursor, noCursor, localXCursor;
+
+  rfb::Cursor cursor;
+  bool cursorVisible;     // Is cursor currently rendered?
+  bool cursorAvailable;   // Is cursor available for rendering?
+  rfb::Point cursorPos;
+  rfb::ManagedPixelBuffer cursorBacking;
+  rfb::Rect cursorBackingRect;
+
+  Time currentSelectionTime;
+  Atom newSelection;
+  bool gettingInitialSelectionTime;
+  bool newServerCutText;
+  char* serverCutText_;
+
+  rfb::Timer setColourMapEntriesTimer;
+  TXViewport* viewport;
+  rfb::Timer pointerEventTimer;
+  rfb::Point lastPointerPos;
+  int lastButtonMask;
+  rdr::U32 downKeysym[256];
+};
+
+#endif
diff --git a/unix/vncviewer/InfoDialog.h b/unix/vncviewer/InfoDialog.h
new file mode 100644
index 0000000..a95f57b
--- /dev/null
+++ b/unix/vncviewer/InfoDialog.h
@@ -0,0 +1,60 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+//
+// InfoDialog.h
+//
+
+#ifndef __INFODIALOG_H__
+#define __INFODIALOG_H__
+
+#include "TXDialog.h"
+#include "TXLabel.h"
+#include "TXButton.h"
+
+extern char buildtime[];
+
+class InfoDialog : public TXDialog, public TXButtonCallback {
+public:
+  InfoDialog(Display* dpy)
+    : TXDialog(dpy, 1, 1, _("VNC connection info")),
+      infoLabel(dpy, "", this, 1, 1, TXLabel::left),
+      okButton(dpy, "OK", this, this, 60)
+  {
+    infoLabel.xPad = 8;
+    infoLabel.move(0, yPad*4);
+    setBorderWidth(1);
+  }
+
+  void setText(char* infoText) {
+    infoLabel.setText(infoText);
+    resize(infoLabel.width(),
+           infoLabel.height() + okButton.height() + yPad*12);
+
+    okButton.move((width() - okButton.width()) / 2,
+                  height() - yPad*4 - okButton.height());
+  }
+
+  virtual void buttonActivate(TXButton* b) {
+    unmap();
+  }
+
+  TXLabel infoLabel;
+  TXButton okButton;
+};
+
+#endif
diff --git a/unix/vncviewer/Makefile.in b/unix/vncviewer/Makefile.in
new file mode 100644
index 0000000..782b680
--- /dev/null
+++ b/unix/vncviewer/Makefile.in
@@ -0,0 +1,30 @@
+
+PACKAGE = @PACKAGE@
+VERSION = @VERSION@
+
+prefix = @prefix@
+datadir = @datadir@
+localedir = $(datadir)/locale
+
+SRCS = DesktopWindow.cxx CConn.cxx vncviewer.cxx
+
+OBJS = $(SRCS:.cxx=.o)
+
+program = vncviewer
+
+DEP_LIBS = ../tx/libtx.a ../rfb/librfb.a ../network/libnetwork.a \
+           ../rdr/librdr.a
+
+EXTRA_LIBS = @ZLIB_LIB@ @JPEG_LIB@ @X_PRE_LIBS@ @X_LIBS@ -lXext -lX11 @X_EXTRA_LIBS@ @LIBINTL@
+
+DIR_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" -I$(top_srcdir) -I$(top_srcdir)/tx -I$(top_srcdir)/intl @X_CFLAGS@ # X_CFLAGS are really CPPFLAGS
+
+all:: $(program)
+
+$(program): $(OBJS) buildtime.o $(DEP_LIBS)
+	rm -f $(program)
+	$(CXXLD) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJS) buildtime.o $(DEP_LIBS) $(LIBS) $(EXTRA_LIBS)
+
+buildtime.o: $(OBJS) $(DEP_LIBS)
+
+# followed by boilerplate.mk
diff --git a/unix/vncviewer/OptionsDialog.h b/unix/vncviewer/OptionsDialog.h
new file mode 100644
index 0000000..68ce8d6
--- /dev/null
+++ b/unix/vncviewer/OptionsDialog.h
@@ -0,0 +1,213 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+//
+// OptionsDialog.h
+//
+
+#ifndef __OPTIONSDIALOG_H__
+#define __OPTIONSDIALOG_H__
+
+#include "TXDialog.h"
+#include "TXLabel.h"
+#include "TXEntry.h"
+#include "TXButton.h"
+#include "TXCheckbox.h"
+#include "parameters.h"
+
+class OptionsDialogCallback {
+public:
+  virtual void setOptions() = 0;
+  virtual void getOptions() = 0;
+};
+
+class OptionsDialog : public TXDialog, public TXButtonCallback,
+                      public TXCheckboxCallback, public TXEntryCallback  {
+public:
+  OptionsDialog(Display* dpy, OptionsDialogCallback* cb_)
+    : TXDialog(dpy, 400, 450, _("VNC Viewer: Connection Options")), cb(cb_),
+      formatAndEnc(dpy, _("Encoding and Color Level:"), this),
+      inputs(dpy, _("Inputs:"), this),
+      misc(dpy, _("Misc:"), this),
+      autoSelect(dpy, _("Auto select"), this, false, this),
+      fullColour(dpy, _("Full (all available colors)"), this, true, this),
+      mediumColour(dpy, _("Medium (256 colors)"), this, true, this),
+      lowColour(dpy, _("Low (64 colors)"), this, true, this),
+      veryLowColour(dpy, _("Very low (8 colors)"), this, true, this),
+      tight(dpy, "Tight", this, true, this),
+      zrle(dpy, "ZRLE", this, true, this),
+      hextile(dpy, "Hextile", this, true, this),
+      raw(dpy, "Raw", this, true, this),
+      customCompressLevel(dpy, _("Custom compression level:"), this, false, this),
+      compressLevel(dpy, this, this, false, 30),
+      compressLevelLabel(dpy, _("level (1=fast, 9=best)"), this),
+      noJpeg(dpy, _("Allow JPEG compression:"), this, false, this),
+      qualityLevel(dpy, this, this, false, 30),
+      qualityLevelLabel(dpy, _("quality (1=poor, 9=best)"), this),
+      viewOnly(dpy, _("View only (ignore mouse & keyboard)"), this, false, this),
+      acceptClipboard(dpy, _("Accept clipboard from server"), this, false, this),
+      sendClipboard(dpy, _("Send clipboard to server"), this, false, this),
+      sendPrimary(dpy, _("Send primary selection & cut buffer as clipboard"),
+                  this, false, this),
+      shared(dpy, _("Shared (don't disconnect other viewers)"), this, false,this),
+      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),
+      okButton(dpy, _("OK"), this, this, 60),
+      cancelButton(dpy, _("Cancel"), this, this, 60)
+  {
+    int y = yPad;
+    formatAndEnc.move(xPad, y);
+    y += formatAndEnc.height();
+    autoSelect.move(xPad, y);
+    int x2 = xPad + autoSelect.width() + xPad*5;
+    y += autoSelect.height();
+    tight.move(xPad, y);
+    fullColour.move(x2, y);
+    y += tight.height();
+    zrle.move(xPad, y);
+    mediumColour.move(x2, y);
+    y += zrle.height();
+    hextile.move(xPad, y);
+    lowColour.move(x2, y);
+    y += hextile.height();
+    raw.move(xPad, y);
+    veryLowColour.move(x2, y);
+    y += raw.height() + yPad*2;
+
+    customCompressLevel.move(xPad, y);
+    y += customCompressLevel.height();
+    compressLevel.move(xPad*10, y);
+    compressLevelLabel.move(xPad*20, y);
+    y += compressLevel.height();
+
+    noJpeg.move(xPad, y);
+    y += noJpeg.height();
+    qualityLevel.move(xPad*10, y);
+    qualityLevelLabel.move(xPad*20, y);
+    y += qualityLevel.height();
+
+    y += yPad*4;
+    inputs.move(xPad, y);
+    y += inputs.height();
+    viewOnly.move(xPad, y);
+    y += viewOnly.height();
+    acceptClipboard.move(xPad, y);
+    y += acceptClipboard.height();
+    sendClipboard.move(xPad, y);
+    y += sendClipboard.height();
+    sendPrimary.move(xPad, y);
+    y += sendPrimary.height();
+
+    y += yPad*4;
+    misc.move(xPad, y);
+    y += misc.height();
+    shared.move(xPad, y);
+    y += shared.height();
+    fullScreen.move(xPad, y);
+    y += fullScreen.height();
+    useLocalCursor.move(xPad, y);
+    y += useLocalCursor.height();
+    dotWhenNoCursor.move(xPad, y);
+    y += dotWhenNoCursor.height();
+
+    okButton.move(width() - xPad*12 - cancelButton.width() - okButton.width(),
+                  height() - yPad*4 - okButton.height());
+    cancelButton.move(width() - xPad*6 - cancelButton.width(),
+                      height() - yPad*4 - cancelButton.height());
+    setBorderWidth(1);
+  }
+
+  virtual void initDialog() {
+    if (cb) cb->setOptions();
+    tight.disabled(autoSelect.checked());
+    zrle.disabled(autoSelect.checked());
+    hextile.disabled(autoSelect.checked());
+    raw.disabled(autoSelect.checked());
+    fullColour.disabled(autoSelect.checked());
+    mediumColour.disabled(autoSelect.checked());
+    lowColour.disabled(autoSelect.checked());
+    veryLowColour.disabled(autoSelect.checked());
+    sendPrimary.disabled(!sendClipboard.checked());
+    dotWhenNoCursor.disabled(!useLocalCursor.checked());
+    compressLevel.disabled(!customCompressLevel.checked());
+    qualityLevel.disabled(!noJpeg.checked());
+  }
+
+  virtual void takeFocus(Time time) {
+    //XSetInputFocus(dpy, entry.win, RevertToParent, time);
+  }
+
+  virtual void buttonActivate(TXButton* b) {
+    if (b == &okButton) {
+      if (cb) cb->getOptions();
+      unmap();
+    } else if (b == &cancelButton) {
+      unmap();
+    }
+  }
+
+  virtual void checkboxSelect(TXCheckbox* checkbox) {
+    if (checkbox == &autoSelect) {
+      tight.disabled(autoSelect.checked());
+      zrle.disabled(autoSelect.checked());
+      hextile.disabled(autoSelect.checked());
+      raw.disabled(autoSelect.checked());
+      fullColour.disabled(autoSelect.checked());
+      mediumColour.disabled(autoSelect.checked());
+      lowColour.disabled(autoSelect.checked());
+      veryLowColour.disabled(autoSelect.checked());
+    } else if (checkbox == &fullColour || checkbox == &mediumColour ||
+               checkbox == &lowColour || checkbox == &veryLowColour) {
+      fullColour.checked(checkbox == &fullColour);
+      mediumColour.checked(checkbox == &mediumColour);
+      lowColour.checked(checkbox == &lowColour);
+      veryLowColour.checked(checkbox == &veryLowColour);
+    } else if (checkbox == &tight || checkbox == &zrle || checkbox == &hextile || checkbox == &raw) {
+      tight.checked(checkbox == &tight);
+      zrle.checked(checkbox == &zrle);
+      hextile.checked(checkbox == &hextile);
+      raw.checked(checkbox == &raw);
+    } else if (checkbox == &sendClipboard) {
+      sendPrimary.disabled(!sendClipboard.checked());
+    } else if (checkbox == &useLocalCursor) {
+      dotWhenNoCursor.disabled(!useLocalCursor.checked());
+    } else if (checkbox == &customCompressLevel) {
+      compressLevel.disabled(!customCompressLevel.checked());
+    } else if (checkbox == &noJpeg) {
+      qualityLevel.disabled(!noJpeg.checked());
+    }
+  }
+
+  virtual void entryCallback(TXEntry* e, Detail detail, Time time) {
+  }
+
+  OptionsDialogCallback* cb;
+  TXLabel formatAndEnc, inputs, misc;
+  TXCheckbox autoSelect;
+  TXCheckbox fullColour, mediumColour, lowColour, veryLowColour;
+  TXCheckbox tight, zrle, hextile, raw;
+
+  TXCheckbox customCompressLevel; TXEntry compressLevel; TXLabel compressLevelLabel;
+  TXCheckbox noJpeg; TXEntry qualityLevel; TXLabel qualityLevelLabel;
+
+  TXCheckbox viewOnly, acceptClipboard, sendClipboard, sendPrimary;
+  TXCheckbox shared, fullScreen, useLocalCursor, dotWhenNoCursor;
+  TXButton okButton, cancelButton;
+};
+
+#endif
diff --git a/unix/vncviewer/PasswdDialog.h b/unix/vncviewer/PasswdDialog.h
new file mode 100644
index 0000000..7b62b8e
--- /dev/null
+++ b/unix/vncviewer/PasswdDialog.h
@@ -0,0 +1,72 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+//
+// PasswdDialog.h
+//
+
+#ifndef __PASSWDDIALOG_H__
+#define __PASSWDDIALOG_H__
+
+#include "TXDialog.h"
+#include "TXLabel.h"
+#include "TXEntry.h"
+
+class PasswdDialog : public TXDialog, public TXEntryCallback {
+public:
+  PasswdDialog(Display* dpy, const char* title, bool userDisabled)
+    : TXDialog(dpy, 320, 100, title, true),
+      userLabel(dpy, _("Username:"), this, 120),
+      userEntry(dpy, this, this, false, 180),
+      passwdLabel(dpy, _("Password:"), this, 120),
+      passwdEntry(dpy, this, this, true, 180)
+  {
+    userLabel.move(0, 20);
+    userEntry.move(userLabel.width(), 18);
+    userEntry.disabled(userDisabled);
+    passwdLabel.move(0, 60);
+    passwdEntry.move(passwdLabel.width(), 58);
+  }
+
+  void takeFocus(Time time) {
+    if (!userEntry.disabled())
+      XSetInputFocus(dpy, userEntry.win(), RevertToParent, time);
+    else
+      XSetInputFocus(dpy, passwdEntry.win(), RevertToParent, time);
+  }
+
+  void entryCallback(TXEntry* e, Detail detail, Time time) {
+    if (e == &userEntry) {
+      if (detail == ENTER || detail == NEXT_FOCUS || detail == PREV_FOCUS)
+        XSetInputFocus(dpy, passwdEntry.win(), RevertToParent, time);
+    } else if (e == &passwdEntry) {
+      if (detail == ENTER) {
+        ok = true;
+        done = true;
+      } else if (detail == NEXT_FOCUS || detail == PREV_FOCUS) {
+        XSetInputFocus(dpy, userEntry.win(), RevertToParent, time);
+      }
+    }
+  }
+
+  TXLabel userLabel;
+  TXEntry userEntry;
+  TXLabel passwdLabel;
+  TXEntry passwdEntry;
+};
+
+#endif
diff --git a/unix/vncviewer/ServerDialog.h b/unix/vncviewer/ServerDialog.h
new file mode 100644
index 0000000..f6980dc
--- /dev/null
+++ b/unix/vncviewer/ServerDialog.h
@@ -0,0 +1,91 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+//
+// ServerDialog.h
+//
+
+#ifndef __SERVERDIALOG_H__
+#define __SERVERDIALOG_H__
+
+#include "TXDialog.h"
+#include "TXLabel.h"
+#include "TXEntry.h"
+#include "TXButton.h"
+#include "OptionsDialog.h"
+#include "AboutDialog.h"
+
+class ServerDialog : public TXDialog, public TXEntryCallback,
+                     public TXButtonCallback {
+public:
+  ServerDialog(Display* dpy, OptionsDialog* options_, AboutDialog* about_)
+    : TXDialog(dpy, 355, 120, _("VNC Viewer: Connection Details"), true),
+      label(dpy, _("VNC server:"), this, 100),
+      entry(dpy, this, this, false, 180),
+      aboutButton(dpy, _("About..."), this, this, 70),
+      optionsButton(dpy, _("Options..."), this, this, 70),
+      okButton(dpy, _("OK"), this, this, 70),
+      cancelButton(dpy, _("Cancel"), this, this, 70),
+      options(options_), about(about_)
+  {
+    label.move(0, 30);
+    entry.move(label.width(), 28);
+    int x = width();
+    int y = height() - yPad*4 - cancelButton.height();
+    x -= cancelButton.width() + xPad*4;
+    cancelButton.move(x, y);
+    x -= okButton.width() + xPad*4;
+    okButton.move(x, y);
+    x -= optionsButton.width() + xPad*4;
+    optionsButton.move(x, y);
+    x -= aboutButton.width() + xPad*4;
+    aboutButton.move(x, y);
+  }
+
+  virtual void takeFocus(Time time) {
+    XSetInputFocus(dpy, entry.win(), RevertToParent, time);
+  }
+
+  virtual void entryCallback(TXEntry* e, Detail detail, Time time) {
+    if (detail == ENTER) {
+      ok = true;
+      done = true;
+    }
+  }
+
+  virtual void buttonActivate(TXButton* b) {
+    if (b == &okButton) {
+      ok = true;
+      done = true;
+    } else if (b == &cancelButton) {
+      ok = false;
+      done = true;
+    } else if (b == &optionsButton) {
+      options->show();
+    } else if (b == &aboutButton) {
+      about->show();
+    }
+  }
+
+  TXLabel label;
+  TXEntry entry;
+  TXButton aboutButton, optionsButton, okButton, cancelButton;
+  OptionsDialog* options;
+  AboutDialog* about;
+};
+
+#endif
diff --git a/unix/vncviewer/buildtime.c b/unix/vncviewer/buildtime.c
new file mode 100644
index 0000000..3f4c369
--- /dev/null
+++ b/unix/vncviewer/buildtime.c
@@ -0,0 +1,18 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+char buildtime[] = __DATE__ " " __TIME__;
diff --git a/unix/vncviewer/parameters.h b/unix/vncviewer/parameters.h
new file mode 100644
index 0000000..6505aa8
--- /dev/null
+++ b/unix/vncviewer/parameters.h
@@ -0,0 +1,48 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+#ifndef __PARAMETERS_H__
+#define __PARAMETERS_H__
+
+#include <rfb/Configuration.h>
+
+extern rfb::IntParameter pointerEventInterval;
+extern rfb::IntParameter wmDecorationWidth;
+extern rfb::IntParameter wmDecorationHeight;
+extern rfb::StringParameter passwordFile;
+extern rfb::BoolParameter useLocalCursor;
+extern rfb::BoolParameter dotWhenNoCursor;
+extern rfb::BoolParameter autoSelect;
+extern rfb::BoolParameter fullColour;
+extern rfb::IntParameter lowColourLevel;
+extern rfb::StringParameter preferredEncoding;
+extern rfb::BoolParameter viewOnly;
+extern rfb::BoolParameter shared;
+extern rfb::BoolParameter acceptClipboard;
+extern rfb::BoolParameter sendClipboard;
+extern rfb::BoolParameter sendPrimary;
+extern rfb::BoolParameter fullScreen;
+extern rfb::StringParameter geometry;
+extern rfb::BoolParameter customCompressLevel;
+extern rfb::IntParameter compressLevel;
+extern rfb::BoolParameter noJpeg;
+extern rfb::IntParameter qualityLevel;
+
+extern char aboutText[];
+extern char* programName;
+
+#endif
diff --git a/unix/vncviewer/vncviewer.cxx b/unix/vncviewer/vncviewer.cxx
new file mode 100644
index 0000000..e9c9ac4
--- /dev/null
+++ b/unix/vncviewer/vncviewer.cxx
@@ -0,0 +1,396 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+//
+// All-new VNC viewer for X.
+//
+
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <rfb/Logger_stdio.h>
+#include <rfb/LogWriter.h>
+#include <network/TcpSocket.h>
+#include "TXWindow.h"
+#include "TXMsgBox.h"
+#include "CConn.h"
+
+#include <intl/gettext.h>
+#define _(String) gettext (String)
+#define gettext_noop(String) String
+#define N_(String) gettext_noop (String)
+
+rfb::LogWriter vlog("main");
+
+using namespace network;
+using namespace rfb;
+using namespace std;
+
+IntParameter pointerEventInterval("PointerEventInterval",
+                                  "Time in milliseconds to rate-limit"
+                                  " successive pointer events", 0);
+IntParameter wmDecorationWidth("WMDecorationWidth", "Width of window manager "
+                               "decoration around a window", 6);
+IntParameter wmDecorationHeight("WMDecorationHeight", "Height of window "
+                                "manager decoration around a window", 24);
+StringParameter passwordFile("PasswordFile",
+                             "Password file for VNC authentication", "");
+AliasParameter rfbauth("passwd", "Alias for PasswordFile", &passwordFile);
+
+BoolParameter useLocalCursor("UseLocalCursor",
+                             "Render the mouse cursor locally", true);
+BoolParameter dotWhenNoCursor("DotWhenNoCursor",
+                              "Show the dot cursor when the server sends an "
+                              "invisible cursor", true);
+BoolParameter autoSelect("AutoSelect",
+			"Auto select pixel format and encoding. "
+			 "Default if PreferredEncoding and FullColor are not specified.", 
+			 true);
+BoolParameter fullColour("FullColor",
+                         "Use full color", true);
+AliasParameter fullColourAlias("FullColour", "Alias for FullColor", &fullColour);
+IntParameter lowColourLevel("LowColorLevel",
+                            "Color level to use on slow connections. "
+                            "0 = Very Low (8 colors), 1 = Low (64 colors), "
+                            "2 = Medium (256 colors)", 2);
+AliasParameter lowColourLevelAlias("LowColourLevel", "Alias for LowColorLevel", &lowColourLevel);
+StringParameter preferredEncoding("PreferredEncoding",
+                                  "Preferred encoding to use (Tight, ZRLE, Hextile or"
+                                  " Raw)", "Tight");
+BoolParameter fullScreen("FullScreen", "Full screen mode", false);
+BoolParameter viewOnly("ViewOnly",
+                       "Don't send any mouse or keyboard events to the server",
+                       false);
+BoolParameter shared("Shared",
+                     "Don't disconnect other viewers upon connection - "
+                     "share the desktop instead",
+                     false);
+BoolParameter acceptClipboard("AcceptClipboard",
+                              "Accept clipboard changes from the server",
+                              true);
+BoolParameter sendClipboard("SendClipboard",
+                            "Send clipboard changes to the server", true);
+BoolParameter sendPrimary("SendPrimary",
+                          "Send the primary selection and cut buffer to the "
+                          "server as well as the clipboard selection",
+                          true);
+
+BoolParameter listenMode("listen", "Listen for connections from VNC servers",
+                         false);
+StringParameter geometry("geometry", "X geometry specification", "");
+StringParameter displayname("display", "The X display", "");
+
+StringParameter via("via", "Gateway to tunnel via", "");
+
+BoolParameter customCompressLevel("CustomCompressLevel",
+				 "Use custom compression level. "
+				 "Default if CompressLevel is specified.", false);
+
+IntParameter compressLevel("CompressLevel",
+			   "Use specified compression level"
+			   "0 = Low, 9 = High",
+			   6);
+
+BoolParameter noJpeg("NoJPEG",
+		     "Disable lossy JPEG compression in Tight encoding.",
+		     false);
+
+IntParameter qualityLevel("QualityLevel",
+			  "JPEG quality level. "
+			  "0 = Low, 9 = High",
+			  6);
+
+char aboutText[1024];
+char* programName;
+extern char buildtime[];
+
+static void CleanupSignalHandler(int sig)
+{
+  // CleanupSignalHandler allows C++ object cleanup to happen because it calls
+  // exit() rather than the default which is to abort.
+  vlog.info("CleanupSignalHandler called");
+  exit(1);
+}
+
+// XLoginIconifier is a class which iconifies the XDM login window when it has
+// grabbed the keyboard, thus releasing the grab, allowing the viewer to use
+// the keyboard.  It remaps the xlogin window on exit.
+class XLoginIconifier {
+public:
+  Display* dpy;
+  Window xlogin;
+  XLoginIconifier() : dpy(0), xlogin(0) {}
+  void iconify(Display* dpy_) {
+    dpy = dpy_;
+    if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), False, GrabModeSync,
+                      GrabModeSync, CurrentTime) == GrabSuccess) {
+      XUngrabKeyboard(dpy, CurrentTime);
+    } else {
+      xlogin = TXWindow::windowWithName(dpy, DefaultRootWindow(dpy), "xlogin");
+      if (xlogin) {
+        XIconifyWindow(dpy, xlogin, DefaultScreen(dpy));
+        XSync(dpy, False);
+      }
+    }
+  }
+  ~XLoginIconifier() {
+    if (xlogin) {
+      fprintf(stderr,"~XLoginIconifier remapping xlogin\n");
+      XMapWindow(dpy, xlogin);
+      XFlush(dpy);
+      sleep(1);
+    }
+  }
+};
+
+static XLoginIconifier xloginIconifier;
+
+static void usage()
+{
+  fprintf(stderr,
+          "\nusage: %s [parameters] [host:displayNum] [parameters]\n"
+          "       %s [parameters] -listen [port] [parameters]\n",
+          programName,programName);
+  fprintf(stderr,"\n"
+          "Parameters can be turned on with -<param> or off with -<param>=0\n"
+          "Parameters which take a value can be specified as "
+          "-<param> <value>\n"
+          "Other valid forms are <param>=<value> -<param>=<value> "
+          "--<param>=<value>\n"
+          "Parameter names are case-insensitive.  The parameters are:\n\n");
+  Configuration::listParams(79, 14);
+  exit(1);
+}
+
+/* Tunnelling support. */
+static void
+interpretViaParam (char **gatewayHost, char **remoteHost,
+		   int *remotePort, char **vncServerName,
+		   int localPort)
+{
+  const int SERVER_PORT_OFFSET = 5900;
+  char *pos = strchr (*vncServerName, ':');
+  if (pos == NULL)
+    *remotePort = SERVER_PORT_OFFSET;
+  else {
+    int portOffset = SERVER_PORT_OFFSET;
+    size_t len;
+    *pos++ = '\0';
+    len = strlen (pos);
+    if (*pos == ':') {
+      /* Two colons is an absolute port number, not an offset. */
+      pos++;
+      len--;
+      portOffset = 0;
+    }
+    if (!len || strspn (pos, "-0123456789") != len )
+      usage ();
+    *remotePort = atoi (pos) + portOffset;
+  }
+
+  if (**vncServerName != '\0')
+    *remoteHost = *vncServerName;
+
+  *gatewayHost = strDup (via.getValueStr ());
+  *vncServerName = new char[50];
+  sprintf (*vncServerName, "localhost::%d", localPort);
+}
+
+#ifndef HAVE_SETENV
+int
+setenv(const char *envname, const char * envval, int overwrite)
+{
+  if (envname && envval) {
+    char * envp = NULL;
+    envp = (char*)malloc(strlen(envname) + strlen(envval) + 2);
+    if (envp) {
+      // The putenv API guarantees memory leaks when
+      // changing environment variables repeatedly.
+      sprintf(envp, "%s=%s", envname, envval);
+      
+      // Cannot free envp
+      putenv(envp);
+      return(0);
+    }
+  }
+  return(-1);
+}
+#endif
+
+static void
+createTunnel (const char *gatewayHost, const char *remoteHost,
+	      int remotePort, int localPort)
+{
+  char *cmd = getenv ("VNC_VIA_CMD");
+  char *percent;
+  char lport[10], rport[10];
+  sprintf (lport, "%d", localPort);
+  sprintf (rport, "%d", remotePort);
+  setenv ("G", gatewayHost, 1);
+  setenv ("H", remoteHost, 1);
+  setenv ("R", rport, 1);
+  setenv ("L", lport, 1);
+  if (!cmd)
+    cmd = "/usr/bin/ssh -f -L \"$L\":\"$H\":\"$R\" \"$G\" sleep 20";
+  /* Compatibility with TightVNC's method. */
+  while ((percent = strchr (cmd, '%')) != NULL)
+    *percent = '$';
+  system (cmd);
+}
+
+int main(int argc, char** argv)
+{
+  setlocale(LC_ALL, "");
+  bindtextdomain(PACKAGE, LOCALEDIR);
+  textdomain(PACKAGE);
+
+  snprintf(aboutText, sizeof(aboutText), 
+           _("TightVNC viewer for X version 1.5 - built %s\n"
+             "Copyright (C) 2002-2005 RealVNC Ltd.\n"
+             "Copyright (C) 2000-2004 Constantin Kaplinsky\n"
+             "Copyright (C) 2004-2005 Peter Astrand, Cendio AB\n"
+             "See http://www.tightvnc.com for information on TightVNC."),
+           buildtime);
+  fprintf(stderr,"\n%s\n", aboutText);
+
+  bind_textdomain_codeset(PACKAGE, "iso-8859-1");
+
+  rfb::initStdIOLoggers();
+  rfb::LogWriter::setLogParams("*:stderr:30");
+
+  signal(SIGHUP, CleanupSignalHandler);
+  signal(SIGINT, CleanupSignalHandler);
+  signal(SIGTERM, CleanupSignalHandler);
+
+  programName = argv[0];
+  char* vncServerName = 0;
+  Display* dpy = 0;
+
+  for (int i = 1; i < argc; i++) {
+    if (Configuration::setParam(argv[i]))
+      continue;
+
+    if (argv[i][0] == '-') {
+      if (i+1 < argc) {
+        if (Configuration::setParam(&argv[i][1], argv[i+1])) {
+          i++;
+          continue;
+        }
+      }
+      usage();
+    }
+
+    vncServerName = argv[i];
+  }
+
+  // Create .vnc in the user's home directory if it doesn't already exist
+  char* homeDir = getenv("HOME");
+  if (homeDir) {
+    CharArray vncDir(strlen(homeDir)+6);
+    sprintf(vncDir.buf, "%s/.vnc", homeDir);
+    int result =  mkdir(vncDir.buf, 0755);
+    if (result == -1 && errno != EEXIST)
+      vlog.error("Could not create .vnc directory: %s.", strerror(errno));
+  } else
+    vlog.error("Could not create .vnc directory: environment variable $HOME not set.");
+
+  if (!::autoSelect.hasBeenSet()) {
+    // Default to AutoSelect=0 if -PreferredEncoding or -FullColor is used
+    ::autoSelect.setParam(!::preferredEncoding.hasBeenSet() 
+			&& !::fullColour.hasBeenSet()
+			&& !::fullColourAlias.hasBeenSet());
+  }
+  if (!::customCompressLevel.hasBeenSet()) {
+    // Default to CustomCompressLevel=1 if CompressLevel is used.
+    ::customCompressLevel.setParam(::compressLevel.hasBeenSet());
+  }
+
+  try {
+    /* Tunnelling support. */
+    if (strlen (via.getValueStr ()) > 0) {
+      char *gatewayHost = "";
+      char *remoteHost = "localhost";
+      int localPort = findFreeTcpPort ();
+      int remotePort;
+      if (!vncServerName)
+        usage();
+      interpretViaParam (&gatewayHost, &remoteHost, &remotePort,
+			 &vncServerName, localPort);
+      createTunnel (gatewayHost, remoteHost, remotePort, localPort);
+    }
+
+    Socket* sock = 0;
+
+    if (listenMode) {
+      int port = 5500;
+      if (vncServerName && isdigit(vncServerName[0]))
+        port = atoi(vncServerName);
+
+      TcpListener listener(port);
+
+      vlog.info("Listening on port %d\n",port);
+
+      while (true) {
+        sock = listener.accept();
+        int pid = fork();
+        if (pid < 0) { perror("fork"); exit(1); }
+        if (pid == 0) break; // child
+        delete sock;
+        int status;
+        while (wait3(&status, WNOHANG, 0) > 0) ;
+      }
+    }
+
+    CharArray displaynameStr(displayname.getData());
+    if (!(dpy = XOpenDisplay(TXWindow::strEmptyToNull(displaynameStr.buf)))) {
+      fprintf(stderr,"%s: unable to open display \"%s\"\n",
+              programName, XDisplayName(displaynameStr.buf));
+      exit(1);
+    }
+
+    TXWindow::init(dpy, "Vncviewer");
+    xloginIconifier.iconify(dpy);
+    CConn cc(dpy, argc, argv, sock, vncServerName, listenMode);
+
+    // X events are processed whenever reading from the socket would block.
+
+    while (true) {
+      cc.getInStream()->check(1);
+      cc.processMsg();
+    }
+
+  } catch (rdr::EndOfStream& e) {
+    vlog.info(e.str());
+  } catch (rdr::Exception& e) {
+    vlog.error(e.str());
+    if (dpy) {
+      TXMsgBox msgBox(dpy, e.str(), MB_OK, "VNC Viewer: Information");
+      msgBox.show();
+    }
+    return 1;
+  }
+
+  return 0;
+}
diff --git a/unix/vncviewer/vncviewer.man b/unix/vncviewer/vncviewer.man
new file mode 100644
index 0000000..a805d32
--- /dev/null
+++ b/unix/vncviewer/vncviewer.man
@@ -0,0 +1,212 @@
+.TH vncviewer 1 "05 May 2004" "TightVNC" "Virtual Network Computing"
+.SH NAME
+vncviewer \- VNC viewer for X
+.SH SYNOPSIS
+.B vncviewer
+.RI [ options ] 
+.RI [ host ][: display# ]
+.br
+.B vncviewer
+.RI [ options ] 
+.B \-listen
+.RI [ port ]
+.SH DESCRIPTION
+.B vncviewer
+is a viewer (client) for Virtual Network Computing.  This manual page documents
+version 4 for the X window system.
+
+If you run the viewer with no arguments it will prompt you for a VNC server to
+connect to.  Alternatively, specify the VNC server as an argument, e.g.:
+
+.RS
+vncviewer snoopy:2
+.RE
+
+where 'snoopy' is the name of the machine, and '2' is the display number of the
+VNC server on that machine.  Either the machine name or display number can be
+omitted.  So for example ":1" means display number 1 on the same machine, and
+"snoopy" means "snoopy:0" i.e. display 0 on machine "snoopy".
+
+If the VNC server is successfully contacted, you will be prompted for a
+password to authenticate you.  If the password is correct, a window will appear
+showing the desktop of the VNC server.
+
+.SH AUTOMATIC PROTOCOL SELECTION
+
+The viewer tests the speed of the connection to the server and chooses the
+encoding and pixel format (color level) appropriately.  This makes it much
+easier to use than previous versions where the user had to specify arcane
+command line arguments.
+
+The viewer normally starts out assuming the link is slow, using the
+encoding with the best compression.  If it turns out that the link is
+fast enough it switches to an encoding which compresses less but is
+faster to generate, thus improving the interactive feel.
+
+The viewer normally starts in full-color mode, but switches to
+low-color mode if the bandwidth is insufficient. However, this only
+occurs when communicating with servers supporting protocol 3.8 or
+newer, since many old servers does not support color mode changes
+safely.
+
+Automatic selection can be turned off by setting the
+\fBAutoSelect\fP parameter to false, or from the options dialog.
+
+.SH POPUP MENU
+The viewer has a popup menu containing entries which perform various actions.
+It is usually brought up by pressing F8, but this can be configured with the
+MenuKey parameter.  Actions which the popup menu can perform include:
+.RS 2
+.IP * 2
+switching in and out of full-screen mode
+.IP *
+quitting the viewer
+.IP *
+generating key events, e.g. sending ctrl-alt-del
+.IP *
+accessing the options dialog and various other dialogs
+.RE
+.PP
+By default, key presses in the popup menu get sent to the VNC server and
+dismiss the popup.  So to get an F8 through to the VNC server simply press it
+twice.
+
+.SH FULL SCREEN MODE
+A full-screen mode is supported.  This is particularly useful when connecting
+to a remote screen which is the same size as your local one. If the remote
+screen is bigger, you can scroll by bumping the mouse against the edge of the
+screen.
+
+Unfortunately this mode doesn't work completely with all window managers, since
+it breaks the X window management conventions.
+
+.SH OPTIONS (PARAMETERS)
+You can get a list of parameters by giving \fB\-h\fP as a command-line option
+to vncviewer.  Parameters can be turned on with -\fIparam\fP or off with
+-\fIparam\fP=0.  Parameters which take a value can be specified as
+-\fIparam\fP \fIvalue\fP.  Other valid forms are \fIparam\fP\fB=\fP\fIvalue\fP
+-\fIparam\fP=\fIvalue\fP --\fIparam\fP=\fIvalue\fP.  Parameter names are
+case-insensitive.
+
+Many of the parameters can also be set graphically via the options dialog box.
+This can be accessed from the popup menu or from the "Connection details"
+dialog box.
+
+.TP
+.B \-display \fIXdisplay\fP
+Specifies the X display on which the VNC viewer window should appear.
+
+.TP
+.B \-geometry \fIgeometry\fP
+Standard X position and sizing specification.
+
+.TP
+.B \-listen \fI[port]\fP
+Causes vncviewer to listen on the given port (default 5500) for reverse
+connections from a VNC server.  WinVNC supports reverse connections initiated
+using the 'Add New Client' menu option or the '\-connect' command-line option.
+Xvnc supports reverse connections with a helper program called
+.B vncconfig.
+
+.TP
+.B \-passwd \fIpassword-file\fP
+If you are on a filesystem which gives you access to the password file used by
+the server, you can specify it here to avoid typing it in.  It will usually be
+"~/.vnc/passwd".
+
+.TP
+.B \-Shared
+When you make a connection to a VNC server, all other existing connections are
+normally closed.  This option requests that they be left open, allowing you to
+share the desktop with someone already using it.
+
+.TP
+.B \-ViewOnly
+Specifies that no keyboard or mouse events should be sent to the server.
+Useful if you want to view a desktop without interfering; often needs to be
+combined with
+.B \-Shared.
+
+.TP
+.B \-FullScreen
+Start in full-screen mode.
+
+.TP
+.B \-AutoSelect
+Use automatic selection of encoding and pixel format (default is on).  Normally
+the viewer tests the speed of the connection to the server and chooses the
+encoding and pixel format appropriately.  Turn it off with \fB-AutoSelect=0\fP.
+
+.TP
+.B \-FullColor, \-FullColour
+Tells the VNC server to send full-color pixels in the best format for this
+display.  This is default. 
+
+.TP
+.B \-LowColorLevel, \-LowColourLevel \fIlevel\fP
+Selects the reduced color level to use on slow links.  \fIlevel\fP can range
+from 0 to 2, 0 meaning 8 colors, 1 meaning 64 colors (the default), 2 meaning
+256 colors.
+
+.TP
+.B \-PreferredEncoding \fIencoding\fP
+This option specifies the preferred encoding to use from one of "Tight", "ZRLE",
+"hextile" or "raw".
+
+.TP
+.B -UseLocalCursor
+Render the mouse cursor locally if the server supports it (default is on).
+This can make the interactive performance feel much better over slow links.
+
+.TP
+.B \-WMDecorationWidth \fIw\fP, \-WMDecorationHeight \fIh\fP
+The total width and height taken up by window manager decorations.  This is
+used to calculate the maximum size of the VNC viewer window.  Default is
+width 6, height 24.
+
+.TP
+.B \-log \fIlogname\fP:\fIdest\fP:\fIlevel\fP
+Configures the debug log settings.  \fIdest\fP can currently be \fBstderr\fP or
+\fBstdout\fP, and \fIlevel\fP is between 0 and 100, 100 meaning most verbose
+output.  \fIlogname\fP is usually \fB*\fP meaning all, but you can target a
+specific source file if you know the name of its "LogWriter".  Default is
+\fB*:stderr:30\fP.
+
+.TP
+.B \-MenuKey \fIkeysym-name\fP
+This option specifies the key which brings up the popup menu.  The key is
+specified as an X11 keysym name (these can be obtained by removing the XK_
+prefix from the entries in "/usr/include/X11/keysymdef.h").  Default is F8.
+
+.TP
+\fB\-via\fR \fIgateway\fR
+Automatically create encrypted TCP tunnel to the \fIgateway\fR machine
+before connection, connect to the \fIhost\fR through that tunnel
+(TightVNC\-specific). By default, this option invokes SSH local port
+forwarding, assuming that SSH client binary can be accessed as
+/usr/bin/ssh. Note that when using the \fB\-via\fR option, the host
+machine name should be specified as known to the gateway machine, e.g. 
+"localhost" denotes the \fIgateway\fR, not the machine where vncviewer
+was launched. The environment variable \fIVNC_VIA_CMD\fR can override
+the default tunnel command of
+\fB/usr/bin/ssh\ -f\ -L\ "$L":"$H":"$R"\ "$G"\ sleep\ 20\fR.  The tunnel
+command is executed with the environment variables \fIL\fR, \fIH\fR,
+\fIR\fR, and \fIG\fR taken the values of the local port number, the remote
+host, the port number on the remote host, and the gateway machine
+respectively.
+
+.SH SEE ALSO
+.BR Xvnc (1),
+.BR vncpasswd (1),
+.BR vncconfig (1),
+.BR vncserver (1)
+.br
+http://www.tightvnc.com
+
+.SH AUTHOR
+Tristan Richardson, RealVNC Ltd.
+
+VNC was originally developed by the RealVNC team while at Olivetti
+Research Ltd / AT&T Laboratories Cambridge.  TightVNC additions was
+implemented by Constantin Kaplinsky. Many other people participated in
+development, testing and support.