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.