Index: /pymolproxy/trunk/Makefile.in
===================================================================
--- /pymolproxy/trunk/Makefile.in	(revision 4588)
+++ /pymolproxy/trunk/Makefile.in	(revision 4588)
@@ -0,0 +1,73 @@
+
+TARGETS =	pymolproxy pymolproxy2
+
+CC =		@CC@
+CC_SWITCHES =	$(CFLAGS) $(EXTRA_CFLAGS) $(DEFINES) $(INCLUDES)
+CFLAGS =	@CFLAGS@ 
+DEFINES =	-DSTANDALONE \
+		-DSVN_VERSION=\"$(SVN_VERSION)\" \
+		-DSTATSDIR=\"$(STATSDIR)\" \
+		-DLOGDIR=\"$(LOGDIR)\" 
+EXTRA_CFLAGS =	-Wall 
+INCLUDES =	$(TCL_INC_SPEC) -I$(srcdir)
+TCL_INC_SPEC =	@TCL_INC_SPEC@
+TCL_LIB_SPEC =	@TCL_LIB_SPEC@
+
+bindir =	@bindir@
+datadir =	@datadir@
+datarootdir =	@datarootdir@
+exec_prefix =	@exec_prefix@
+includedir =	@includedir@
+libdir =	@libdir@
+mandir =	@mandir@
+prefix =	@prefix@
+srcdir =	@srcdir@
+
+INSTALL =	@INSTALL@
+MKDIR_P =	@MKDIR_P@
+VPATH =		$(srcdir)
+
+SVN_VERSION	= $(shell svnversion $(srcdir) | sed 's/Unversioned directory/unknown/')
+STATSDIR	= @STATSDIR@
+LOGDIR		= @LOGDIR@
+
+.PHONY: all install clean distclean
+
+FILES =		pymolproxy pymolproxy2 
+PROXY_OBJS =	pymolproxy.o md5.o
+PROXY2_OBJS =	pymolproxy2.o md5.o 
+LIBS =		$(TCL_LIB_SPEC) \
+		-Wl,-rpath,$(libdir) 
+
+SCRIPTS		= $(srcdir)/scripts/box.py
+
+all: $(TARGETS)
+
+pymolproxy: $(PROXY_OBJS) 
+	$(CC) $(CC_SWITCHES) -o $@ $^ $(LIBS)
+
+pymolproxy2: $(PROXY2_OBJS) 
+	$(CC) $(CC_SWITCHES) -o $@ $^ $(LIBS) -lpthread
+
+.c.o:
+	$(CC) $(CC_SWITCHES) -o $@ -c $<
+
+install: install-pymolproxy install-scripts
+
+install-pymolproxy: pymolproxy pymolproxy2
+	$(MKDIR_P) -m 0755 $(bindir)
+	for i in $(FILES) ; do \
+	    $(INSTALL) -m 0555 $$i $(bindir) ; \
+	done
+
+install-scripts:
+	$(MKDIR_P) -m 0755 $(libdir)/pymol/rappture
+	for i in $(SCRIPTS) ; do \
+	    $(INSTALL) -m 0555 $$i $(libdir)/pymol/rappture; \
+	done
+
+clean:
+	$(RM) a.out *.o *~ core* $(TARGETS) *.log *.tmp logfile* .deps/*.d
+
+distclean: clean
+	$(RM) Makefile pymol
Index: /pymolproxy/trunk/md5.c
===================================================================
--- /pymolproxy/trunk/md5.c	(revision 4588)
+++ /pymolproxy/trunk/md5.c	(revision 4588)
@@ -0,0 +1,381 @@
+/*
+  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+	http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.c is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
+	either statically or dynamically; added missing #include <string.h>
+	in library.
+  2002-03-11 lpd Corrected argument list for main(), and added int return
+	type, in test program and T value program.
+  2002-02-21 lpd Added missing #include <stdio.h> in test program.
+  2000-07-03 lpd Patched to eliminate warnings about "constant is
+	unsigned in ANSI C, signed in traditional"; made test program
+	self-checking.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+  1999-05-03 lpd Original version.
+ */
+
+#include "md5.h"
+#include <string.h>
+
+#undef BYTE_ORDER	/* 1 = big-endian, -1 = little-endian, 0 = unknown */
+#ifdef ARCH_IS_BIG_ENDIAN
+#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
+#else
+#  define BYTE_ORDER 0
+#endif
+
+#define T_MASK ((md5_word_t)~0)
+#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
+#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
+#define T3    0x242070db
+#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
+#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
+#define T6    0x4787c62a
+#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
+#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
+#define T9    0x698098d8
+#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
+#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
+#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
+#define T13    0x6b901122
+#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
+#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
+#define T16    0x49b40821
+#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
+#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
+#define T19    0x265e5a51
+#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
+#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
+#define T22    0x02441453
+#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
+#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
+#define T25    0x21e1cde6
+#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
+#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
+#define T28    0x455a14ed
+#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
+#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
+#define T31    0x676f02d9
+#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
+#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
+#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
+#define T35    0x6d9d6122
+#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
+#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
+#define T38    0x4bdecfa9
+#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
+#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
+#define T41    0x289b7ec6
+#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
+#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
+#define T44    0x04881d05
+#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
+#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
+#define T47    0x1fa27cf8
+#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
+#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
+#define T50    0x432aff97
+#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
+#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
+#define T53    0x655b59c3
+#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
+#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
+#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
+#define T57    0x6fa87e4f
+#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
+#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
+#define T60    0x4e0811a1
+#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
+#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
+#define T63    0x2ad7d2bb
+#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
+
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+    md5_word_t
+	a = pms->abcd[0], b = pms->abcd[1],
+	c = pms->abcd[2], d = pms->abcd[3];
+    md5_word_t t;
+#if BYTE_ORDER > 0
+    /* Define storage only for big-endian CPUs. */
+    md5_word_t X[16];
+#else
+    /* Define storage for little-endian or both types of CPUs. */
+    md5_word_t xbuf[16];
+    const md5_word_t *X;
+#endif
+
+    {
+#if BYTE_ORDER == 0
+	/*
+	 * Determine dynamically whether this is a big-endian or
+	 * little-endian machine, since we can use a more efficient
+	 * algorithm on the latter.
+	 */
+	static const int w = 1;
+
+	if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
+#endif
+#if BYTE_ORDER <= 0		/* little-endian */
+	{
+	    /*
+	     * On little-endian machines, we can process properly aligned
+	     * data without copying it.
+	     */
+	    if (!((data - (const md5_byte_t *)0) & 3)) {
+		/* data are properly aligned */
+		X = (const md5_word_t *)data;
+	    } else {
+		/* not aligned */
+		memcpy(xbuf, data, 64);
+		X = xbuf;
+	    }
+	}
+#endif
+#if BYTE_ORDER == 0
+	else			/* dynamic big-endian */
+#endif
+#if BYTE_ORDER >= 0		/* big-endian */
+	{
+	    /*
+	     * On big-endian machines, we must arrange the bytes in the
+	     * right order.
+	     */
+	    const md5_byte_t *xp = data;
+	    int i;
+
+#  if BYTE_ORDER == 0
+	    X = xbuf;		/* (dynamic only) */
+#  else
+#    define xbuf X		/* (static only) */
+#  endif
+	    for (i = 0; i < 16; ++i, xp += 4)
+		xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
+	}
+#endif
+    }
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+    /* Round 1. */
+    /* Let [abcd k s i] denote the operation
+       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + F(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+    /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  7,  T1);
+    SET(d, a, b, c,  1, 12,  T2);
+    SET(c, d, a, b,  2, 17,  T3);
+    SET(b, c, d, a,  3, 22,  T4);
+    SET(a, b, c, d,  4,  7,  T5);
+    SET(d, a, b, c,  5, 12,  T6);
+    SET(c, d, a, b,  6, 17,  T7);
+    SET(b, c, d, a,  7, 22,  T8);
+    SET(a, b, c, d,  8,  7,  T9);
+    SET(d, a, b, c,  9, 12, T10);
+    SET(c, d, a, b, 10, 17, T11);
+    SET(b, c, d, a, 11, 22, T12);
+    SET(a, b, c, d, 12,  7, T13);
+    SET(d, a, b, c, 13, 12, T14);
+    SET(c, d, a, b, 14, 17, T15);
+    SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+     /* Round 2. */
+     /* Let [abcd k s i] denote the operation
+          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + G(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  1,  5, T17);
+    SET(d, a, b, c,  6,  9, T18);
+    SET(c, d, a, b, 11, 14, T19);
+    SET(b, c, d, a,  0, 20, T20);
+    SET(a, b, c, d,  5,  5, T21);
+    SET(d, a, b, c, 10,  9, T22);
+    SET(c, d, a, b, 15, 14, T23);
+    SET(b, c, d, a,  4, 20, T24);
+    SET(a, b, c, d,  9,  5, T25);
+    SET(d, a, b, c, 14,  9, T26);
+    SET(c, d, a, b,  3, 14, T27);
+    SET(b, c, d, a,  8, 20, T28);
+    SET(a, b, c, d, 13,  5, T29);
+    SET(d, a, b, c,  2,  9, T30);
+    SET(c, d, a, b,  7, 14, T31);
+    SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+     /* Round 3. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + H(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  5,  4, T33);
+    SET(d, a, b, c,  8, 11, T34);
+    SET(c, d, a, b, 11, 16, T35);
+    SET(b, c, d, a, 14, 23, T36);
+    SET(a, b, c, d,  1,  4, T37);
+    SET(d, a, b, c,  4, 11, T38);
+    SET(c, d, a, b,  7, 16, T39);
+    SET(b, c, d, a, 10, 23, T40);
+    SET(a, b, c, d, 13,  4, T41);
+    SET(d, a, b, c,  0, 11, T42);
+    SET(c, d, a, b,  3, 16, T43);
+    SET(b, c, d, a,  6, 23, T44);
+    SET(a, b, c, d,  9,  4, T45);
+    SET(d, a, b, c, 12, 11, T46);
+    SET(c, d, a, b, 15, 16, T47);
+    SET(b, c, d, a,  2, 23, T48);
+#undef SET
+
+     /* Round 4. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + I(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  6, T49);
+    SET(d, a, b, c,  7, 10, T50);
+    SET(c, d, a, b, 14, 15, T51);
+    SET(b, c, d, a,  5, 21, T52);
+    SET(a, b, c, d, 12,  6, T53);
+    SET(d, a, b, c,  3, 10, T54);
+    SET(c, d, a, b, 10, 15, T55);
+    SET(b, c, d, a,  1, 21, T56);
+    SET(a, b, c, d,  8,  6, T57);
+    SET(d, a, b, c, 15, 10, T58);
+    SET(c, d, a, b,  6, 15, T59);
+    SET(b, c, d, a, 13, 21, T60);
+    SET(a, b, c, d,  4,  6, T61);
+    SET(d, a, b, c, 11, 10, T62);
+    SET(c, d, a, b,  2, 15, T63);
+    SET(b, c, d, a,  9, 21, T64);
+#undef SET
+
+     /* Then perform the following additions. (That is increment each
+        of the four registers by the value it had before this block
+        was started.) */
+    pms->abcd[0] += a;
+    pms->abcd[1] += b;
+    pms->abcd[2] += c;
+    pms->abcd[3] += d;
+}
+
+void
+md5_init(md5_state_t *pms)
+{
+    pms->count[0] = pms->count[1] = 0;
+    pms->abcd[0] = 0x67452301;
+    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
+    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
+    pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+    const md5_byte_t *p = data;
+    int left = nbytes;
+    int offset = (pms->count[0] >> 3) & 63;
+    md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+    if (nbytes <= 0)
+	return;
+
+    /* Update the message length. */
+    pms->count[1] += nbytes >> 29;
+    pms->count[0] += nbits;
+    if (pms->count[0] < nbits)
+	pms->count[1]++;
+
+    /* Process an initial partial block. */
+    if (offset) {
+	int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+	memcpy(pms->buf + offset, p, copy);
+	if (offset + copy < 64)
+	    return;
+	p += copy;
+	left -= copy;
+	md5_process(pms, pms->buf);
+    }
+
+    /* Process full blocks. */
+    for (; left >= 64; p += 64, left -= 64)
+	md5_process(pms, p);
+
+    /* Process a final partial block. */
+    if (left)
+	memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+    static const md5_byte_t pad[64] = {
+	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+    };
+    md5_byte_t data[8];
+    int i;
+
+    /* Save the length before padding. */
+    for (i = 0; i < 8; ++i)
+	data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+    /* Pad to 56 bytes mod 64. */
+    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+    /* Append the length. */
+    md5_append(pms, data, 8);
+    for (i = 0; i < 16; ++i)
+	digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
Index: /pymolproxy/trunk/md5.h
===================================================================
--- /pymolproxy/trunk/md5.h	(revision 4588)
+++ /pymolproxy/trunk/md5.h	(revision 4588)
@@ -0,0 +1,91 @@
+/*
+  Copyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+	http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.h is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Removed support for non-ANSI compilers; removed
+	references to Ghostscript; clarified derivation from RFC 1321;
+	now handles byte order either statically or dynamically.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+	added conditionalization for C++ compilation from Martin
+	Purschke <purschke@bnl.gov>.
+  1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+#  define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+    md5_word_t count[2];	/* message length in bits, lsw first */
+    md5_word_t abcd[4];		/* digest buffer */
+    md5_byte_t buf[64];		/* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C" 
+{
+#endif
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+#ifdef __cplusplus
+}  /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
Index: /pymolproxy/trunk/pymolproxy.c
===================================================================
--- /pymolproxy/trunk/pymolproxy.c	(revision 4588)
+++ /pymolproxy/trunk/pymolproxy.c	(revision 4588)
@@ -0,0 +1,2310 @@
+
+/*
+ * ----------------------------------------------------------------------
+ * proxypymol.c
+ *
+ *      This module creates the Tcl interface to the pymol server.  It acts as
+ *      a go-between establishing communication between a molvisviewer widget
+ *      and the pymol server. The communication protocol from the molvisviewer
+ *      widget is the Tcl language.  Commands are then relayed to the pymol
+ *      server.  Responses from the pymol server are translated into Tcl
+ *      commands and send to the molvisviewer widget. For example, resulting
+ *      image rendered offscreen is returned as ppm-formatted image data.
+ *
+ *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
+ *
+ *  See the file "license.terms" for information on usage and
+ *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ * ======================================================================
+ */
+
+/*
+ * Notes:   The proxy should not maintain any state information from the
+ *	    client, other that what it needs for event (rotate, pan, zoom,
+ *	    atom scale, bond thickness, etc.) compression.  This is because 
+ *	    the connection is periodically broken (timeout, error, etc.).
+ *	    It's the responsibility of the client (molvisviewer) to restore 
+ *	    the settings of the previous view.  The proxy is just a relay
+ *	    between the client and the pymol server.
+ */
+/*
+ *   +--------------+        +------------+         +--------+
+ *   |              | output |            |  input  |        | 
+ *   |              |------->|            |-------->|        |
+ *   | molvisviewer |        | pymolproxy |  output | pymol  |
+ *   |    client    |        |            |<--------| server | 
+ *   |              | input  |            |  errors |        | 
+ *   |              |<-------|            |<--------|        | 
+ *   +--------------+        +------------+         +--------+
+ * 
+ * The communication between the pymol server and proxy is completely
+ * synchronous.  The proxy relays/translates commands from the client to the
+ * server and then wait for the responses.  It also watches the server's
+ * stdout and stderr changes so that extraneous writes don't block the
+ * the server. (I don't know exactly what those responses are)
+ *
+ * The communication between the client and the proxy is different.  The
+ * client commands are read when they become available on the socket.  Proxy
+ * writes to the client (image output) are non-blocking so that we don't
+ * deadlock with the client.  The client may be busy sending the next command
+ * when we want to write the resulting image from the last command.
+ *
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/times.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <tcl.h>
+
+#undef INLINE
+#ifdef __GNUC__
+#  define INLINE __inline__
+#else
+#  define INLINE
+#endif
+
+#define FALSE 0
+#define TRUE  1
+
+#define IO_TIMEOUT (30000)
+#define KEEPSTATS	1
+#define CVT2SECS(x)  ((double)(x).tv_sec) + ((double)(x).tv_usec * 1.0e-6)
+
+typedef struct {
+    pid_t child;		/* Child process. */
+    size_t numFrames;		/* # of frames sent to client. */
+    size_t numBytes;		/* # of bytes for all frames. */
+    size_t numCommands;		/* # of commands executed */
+    double cmdTime;		/* Elapsed time spend executing commands. */
+    struct timeval start;	/* Start of elapsed time. */
+} Stats;
+
+static Stats stats;
+
+#define READTRACE	1
+#define EXPECTTRACE	0
+#define WRITETRACE	0
+
+static int debug = FALSE;
+
+static FILE *flog;
+static FILE *scriptFile;
+static int savescript = FALSE;
+
+typedef struct Image {
+    struct Image *nextPtr;		/* Next image in chain of images. The
+					 * list is ordered by the most
+					 * recently received image from the
+					 * pymol server to the least. */
+    struct Image *prevPtr;		/* Previous image in chain of
+					 * images. The list is ordered by the
+					 * most recently received image from
+					 * the pymol server to the least. */
+    ssize_t numWritten;			/* Number of bytes of image data
+					 * already delivered.*/
+    size_t bytesLeft;			/* Number of bytes of image data left
+					 * to delivered to the client. */
+    char data[1];			/* Start of image data. We allocate
+					 * the size of the Image structure
+					 * plus the size of the image data. */
+} Image;
+
+#define BUFFER_SIZE		4096
+
+typedef struct {
+    const char *ident;
+    char bytes[BUFFER_SIZE];
+    int fill;
+    int mark;
+    int fd;
+} ReadBuffer;
+
+#define BUFFER_OK		 0
+#define BUFFER_ERROR		-1
+#define BUFFER_CONTINUE		-2
+
+#define FORCE_UPDATE		(1<<0)
+#define CAN_UPDATE		(1<<1)
+#define INVALIDATE_CACHE	(1<<3)
+#define ATOM_SCALE_PENDING	(1<<4)
+#define STICK_RADIUS_PENDING	(1<<5)
+#define ROTATE_PENDING		(1<<6)
+#define PAN_PENDING		(1<<7)
+#define ZOOM_PENDING		(1<<8)
+#define UPDATE_PENDING		(1<<9)
+#define VIEWPORT_PENDING	(1<<10)
+
+typedef struct {
+    Tcl_Interp *interp;
+    unsigned int flags;			/* Various flags. */
+    Image *headPtr, *tailPtr;		/* List of images to be delivered to
+					 * the client.  The most recent images
+					 * are in the front of the list. */
+
+    int sin, sout;			/* Server file descriptors. */
+    int cin, cout;			/* Client file descriptors. */
+    ReadBuffer client;			/* Read buffer for client input. */
+    ReadBuffer server;			/* Read buffer for server output. */
+    int frame;
+    int rockOffset;
+    int cacheId;
+    int error;
+    int status;
+    int width, height;			/* Size of viewport. */
+    float xAngle, yAngle, zAngle;	/* Euler angles of pending
+					 * rotation.  */
+    float sphereScale;			/* Atom scale of pending re-scale. */
+    float stickRadius;			/* Bond thickness of pending
+					 * re-scale. */
+    float zoom;
+    float xPan, yPan;
+} PymolProxy;
+
+#define ERROR(...)      LogMessage(LOG_ERR, __FILE__, __LINE__, __VA_ARGS__)
+#define TRACE(...)      LogMessage(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
+#define WARN(...)       LogMessage(LOG_WARNING, __FILE__, __LINE__, __VA_ARGS__)
+#define INFO(...)       LogMessage(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
+
+static const char *syslogLevels[] = {
+    "emergency",			/* System is unusable */
+    "alert",				/* Action must be taken immediately */
+    "critical",				/* Critical conditions */
+    "error",				/* Error conditions */
+    "warning",				/* Warning conditions */
+    "notice",				/* Normal but significant condition */
+    "info",				/* Informational */
+    "debug",				/* Debug-level messages */
+};
+
+void
+LogMessage(int priority, const char *path, int lineNum, const char* fmt, ...)
+{
+#define MSG_LEN (2047)
+    char message[MSG_LEN+1];
+    const char *s;
+    int length;
+    va_list lst;
+
+    va_start(lst, fmt);
+    s = strrchr(path, '/');
+    if (s == NULL) {
+        s = path;
+    } else {
+        s++;
+    }
+    length = snprintf(message, MSG_LEN, "pymolproxy (%d) %s: %s:%d ", 
+		      getpid(), syslogLevels[priority],  s, lineNum);
+    length += vsnprintf(message + length, MSG_LEN - length, fmt, lst);
+    message[MSG_LEN] = '\0';
+    if (debug) {
+	fprintf(stderr, "%s\n", message);
+    } else {
+	syslog(priority, message, length);
+    }
+}
+
+static void PollForEvents(PymolProxy *proxyPtr);
+static void
+Debug TCL_VARARGS_DEF(const char *, arg1)
+{
+    if (debug) {
+        const char *format;
+        va_list args;
+
+        format = TCL_VARARGS_START(const char *, arg1, args);
+        fprintf(flog, "pymolproxy: ");
+        vfprintf(flog, format, args);
+        fprintf(flog, "\n");
+        fflush(flog);
+    }
+}
+
+static void
+script TCL_VARARGS_DEF(const char *, arg1)
+{
+    if (savescript) {
+        const char *format;
+        va_list args;
+
+        format = TCL_VARARGS_START(const char *, arg1, args);
+        vfprintf(scriptFile, format, args);
+        fflush(scriptFile);
+    }
+}
+
+static void
+InitBuffer(ReadBuffer *readPtr, const char *ident, int f)
+{
+    readPtr->ident = ident;
+    readPtr->fd = f;
+    readPtr->fill = 0;
+    readPtr->mark = 0;
+}
+
+static void
+FlushBuffer(ReadBuffer *readPtr)
+{
+    readPtr->fill = 0;
+    readPtr->mark = 0;
+}
+
+/*  
+ * FillBuffer --
+ *
+ *	Fills the buffer with available data.  Any existing data
+ *	in the buffer is slide to the front of the buffer, then
+ *	the channel is read to fill the rest of the buffer.
+ *
+ *	If EOF or an error occur when reading the channel, then 
+ *	BUFFER_ERROR is returned. If the buffer can't be filled, 
+ *	then BUFFER_CONTINUE is returned.  
+ *
+ */
+static int
+FillBuffer(ReadBuffer *readPtr)
+{
+    size_t bytesLeft;
+    ssize_t nRead;
+
+#if READTRACE
+    Debug("Entering FillBuffer (mark=%d, fill=%d)\n", readPtr->mark, 
+	  readPtr->fill);
+#endif
+    if (readPtr->mark >= readPtr->fill) {
+	INFO("tossing full buffer mark=%d, fill=%d", readPtr->mark, 
+	     readPtr->fill);
+	readPtr->mark = readPtr->fill = 0;
+    }
+    if (readPtr->mark > 0) {
+	size_t i, j;
+
+	/* Some data has been consumed. Move the unconsumed data to the front
+	 * of the buffer. */
+	for (i = 0, j = readPtr->mark; j < readPtr->fill; i++, j++) {
+	    readPtr->bytes[i] = readPtr->bytes[j];
+	}
+	readPtr->mark = 0;
+	readPtr->fill = i;
+    }
+    bytesLeft = BUFFER_SIZE - readPtr->fill - 1;
+    nRead = read(readPtr->fd, readPtr->bytes + readPtr->fill, bytesLeft);
+    if (nRead == 0) {
+	INFO("EOF found reading \"%s\" buffer", readPtr->ident);
+	return BUFFER_ERROR;		/* EOF, treat as an error. */
+    }
+    if (nRead < 0) {
+	if (errno != EAGAIN) {
+#if READTRACE
+	    Debug("in FillBuffer: read failed %d: %s", errno, strerror(errno));
+	    Debug("Leaving FillBuffer FAIL(read %d bytes) mark=%d, fill=%d\n", 
+		  nRead, readPtr->mark, readPtr->fill);
+#endif
+	    ERROR("error reading \"%s\" buffer: %s", readPtr->ident, 
+		  strerror(errno));
+	    return BUFFER_ERROR;
+	}
+	INFO("Short read for \"%s\" buffer", readPtr->ident);
+	return BUFFER_CONTINUE;
+    }
+    readPtr->fill += nRead;
+#if READTRACE
+    Debug("Leaving FillBuffer (read %d bytes) mark=%d, fill=%d\n", 
+	  nRead, readPtr->mark, readPtr->fill);
+#endif
+    return (nRead == bytesLeft) ? BUFFER_OK : BUFFER_CONTINUE;
+}
+
+/* 
+ * NextLine --
+ *
+ *	Returns the next available line (terminated by a newline).
+ *	If insufficient data is in the buffer, then the channel is
+ *	read for more data.  If reading the channel results in a
+ *	short read, NULL is returned and *numBytesPtr is set to
+ *	BUFFER_CONTINUE.
+ */
+static char *
+NextLine(ReadBuffer *readPtr, int *numBytesPtr)
+{
+    int i;
+    int status;
+
+#if READTRACE
+    Debug("Entering NextLine (mark=%d, fill=%d)\n",readPtr->mark, readPtr->fill);
+#endif
+    status = BUFFER_OK;
+    for (;;) {
+#if READTRACE
+	Debug("in NextLine: mark=%d fill=%d\n", readPtr->mark, readPtr->fill);
+#endif
+	/* Check for a newline in the current buffer (it's the next 
+	 * full line). */
+	for (i = readPtr->mark; i < readPtr->fill; i++) {
+	    if (readPtr->bytes[i] == '\n') {
+		char *p;
+		
+		/* Save the start of the line. */
+		p = readPtr->bytes + readPtr->mark;
+		i++;			/* Include the newline. */
+		*numBytesPtr = i - readPtr->mark;
+		readPtr->mark = i;
+#if READTRACE
+		Debug("Leaving NextLine(%.*s)\n", *numBytesPtr, p);
+#endif
+		return p;
+	    }
+	}
+	/* Couldn't find a newline, so it may be that we need to read some
+	 * more. Check first that last read wasn't a short read. */
+	if (status == BUFFER_CONTINUE) {
+	    *numBytesPtr = BUFFER_CONTINUE;  /* No complete line just yet. */
+	    return NULL;
+	}
+	/* Try to add more data to the buffer. */
+	status = FillBuffer(readPtr);
+	if (status == BUFFER_ERROR) {
+	    *numBytesPtr = BUFFER_ERROR;
+	    return NULL;	/* EOF or error on read. */
+	}
+	/* BUFFER_OK or BUFFER_CONTINUE */
+    }
+#if READTRACE
+    Debug("Leaving NextLine failed to read line\n");
+#endif
+    *numBytesPtr = BUFFER_CONTINUE;
+    return NULL;
+}
+
+/* 
+ * ReadFollowingData --
+ *
+ *	Read the requested number of bytes from the buffer.  Fails if
+ *	the requested number of bytes are not immediately available.
+ *	Never should be short. 
+ */
+static int 
+ReadFollowingData(ReadBuffer *readPtr, char *out, int numBytes)
+{
+#if READTRACE
+    Debug("Entering ReadFollowingData(%d)\n", numBytes);
+#endif
+    while (numBytes > 0) {
+	int bytesLeft;
+	int status;
+
+	bytesLeft = readPtr->fill - readPtr->mark;
+	if (bytesLeft > 0) {
+	    int size;
+
+	    /* Pull bytes out of the buffer, updating the mark. */
+	    size = (bytesLeft >  numBytes) ? numBytes : bytesLeft;
+	    memcpy(out, readPtr->bytes + readPtr->mark, size);
+	    readPtr->mark += size;
+	    numBytes -= size;
+	    out += size;
+	}
+	if (numBytes == 0) {
+	    /* Received requested # bytes. */
+#if READTRACE
+	    Debug("Leaving ReadFollowingData(%d)\n", numBytes);
+#endif
+	    return BUFFER_OK;
+	}
+	/* Didn't get enough bytes, need to read some more. */
+	status = FillBuffer(readPtr);
+	if (status == BUFFER_ERROR) {
+	    return BUFFER_ERROR;
+	}
+#if READTRACE
+	Debug("in ReadFollowingData: after fill: mark=%d fill=%d status=%d\n", 
+	      readPtr->mark, readPtr->fill, status);
+#endif
+    }
+#if READTRACE
+    Debug("Leaving ReadFollowingData(%d)\n", numBytes);
+#endif
+    return BUFFER_OK;
+}
+
+INLINE static void
+clear_error(PymolProxy *proxyPtr)
+{
+    proxyPtr->error = 0;
+    proxyPtr->status = TCL_OK;
+}
+
+static int
+Expect(PymolProxy *proxyPtr, char *match, char *out, int maxSize)
+{
+    char c;
+    size_t length;
+
+    if (proxyPtr->status != TCL_OK) {
+        return TCL_ERROR;
+    }
+#if EXPECTTRACE
+    Debug("Entering Expect(want=\"%s\", maxSize=%d)\n", match, maxSize);
+#endif
+    c = match[0];
+    length = strlen(match);
+    for (;;) {
+	int numBytes;
+	char *line;
+
+	line = NextLine(&proxyPtr->server, &numBytes);
+	if (line != NULL) {
+#if EXPECTTRACE
+	    Debug("pymol says (read %d bytes):%.*s", numBytes, numBytes, line);
+#endif
+	    if ((c == line[0]) && (strncmp(line, match, length) == 0)) {
+		if (maxSize < numBytes) {
+		    numBytes = maxSize;
+		}
+		memcpy(out, line, numBytes);
+		clear_error(proxyPtr);
+#if EXPECTTRACE
+		Debug("Leaving Expect: got (%.*s)\n", numBytes, out);
+#endif
+		return TCL_OK;
+	    }
+	    continue;
+	}
+	if (numBytes == BUFFER_ERROR) {
+	    Tcl_AppendResult(proxyPtr->interp, 
+		"error reading server to find match for \"", match, "\": ", 
+		Tcl_PosixError(proxyPtr->interp), (char *)NULL);
+	    return TCL_ERROR;
+	}
+    }
+    Tcl_AppendResult(proxyPtr->interp, "failed to find match for \"", match, 
+	"\"", (char *)NULL);
+    proxyPtr->error = 2;
+    proxyPtr->status = TCL_ERROR;
+    return TCL_ERROR;
+}
+
+#if KEEPSTATS
+
+static int
+WriteStats(const char *who, int code) 
+{
+    double start, finish;
+    pid_t pid;
+    char buf[BUFSIZ];
+    Tcl_DString ds;
+
+    {
+	struct timeval tv;
+
+	/* Get ending time.  */
+	gettimeofday(&tv, NULL);
+	finish = CVT2SECS(tv);
+	tv = stats.start;
+	start = CVT2SECS(tv);
+    }
+    /* 
+     * Session information:
+     *   1. Start date of session in seconds.
+     *   2. Process ID
+     *	 3. Number of frames returned.
+     *	 4. Number of bytes total returned (in frames).
+     *	 5. Total elapsed time of all commands.
+     *   6. Total elapsed time of session.
+     *	 7. Exit code of pymol server.
+     *   8. User time.  
+     *	 9. System time.
+     *  10. Maximum resident size.
+     */ 
+    pid = getpid();
+    Tcl_DStringInit(&ds);
+    
+    sprintf(buf, "<session server=\"%s\" ", who);
+    Tcl_DStringAppend(&ds, buf, -1);
+
+    strcpy(buf, ctime(&stats.start.tv_sec));
+
+    buf[strlen(buf) - 1] = '\0';
+    Tcl_DStringAppend(&ds, "date=\"", -1);
+    Tcl_DStringAppend(&ds, buf, -1);
+    Tcl_DStringAppend(&ds, "\" ", -1);
+
+    sprintf(buf, "date_secs=\"%ld\" ", stats.start.tv_sec);
+    Tcl_DStringAppend(&ds, buf, -1);
+
+    sprintf(buf, "pid=\"%d\" ", pid);
+    Tcl_DStringAppend(&ds, buf, -1);
+    sprintf(buf, "num_frames=\"%lu\" ", (unsigned long int)stats.numFrames);
+    Tcl_DStringAppend(&ds, buf, -1);
+    sprintf(buf, "frame_bytes=\"%lu\" ", (unsigned long int)stats.numBytes);
+    Tcl_DStringAppend(&ds, buf, -1);
+    sprintf(buf, "num_commands=\"%lu\" ", (unsigned long int)stats.numCommands);
+    Tcl_DStringAppend(&ds, buf, -1);
+    sprintf(buf, "cmd_time=\"%g\" ", stats.cmdTime);
+    Tcl_DStringAppend(&ds, buf, -1);
+    sprintf(buf, "session_time=\"%g\" ", finish - start);
+    Tcl_DStringAppend(&ds, buf, -1);
+    sprintf(buf, "status=\"%d\" ", code);
+    Tcl_DStringAppend(&ds, buf, -1);
+    {
+	long clocksPerSec = sysconf(_SC_CLK_TCK);
+	double clockRes = 1.0 / clocksPerSec;
+	struct tms tms;
+
+	memset(&tms, 0, sizeof(tms));
+	times(&tms);
+	sprintf(buf, "utime=\"%g\" ", tms.tms_utime * clockRes);
+	Tcl_DStringAppend(&ds, buf, -1);
+	sprintf(buf, "stime=\"%g\" ", tms.tms_stime * clockRes);
+	Tcl_DStringAppend(&ds, buf, -1);
+	sprintf(buf, "cutime=\"%g\" ", tms.tms_cutime * clockRes);
+	Tcl_DStringAppend(&ds, buf, -1);
+	sprintf(buf, "cstime=\"%g\" ", tms.tms_cstime * clockRes);
+	Tcl_DStringAppend(&ds, buf, -1);
+    }
+    Tcl_DStringAppend(&ds, "/>\n", -1);
+
+    {
+	int f;
+	ssize_t length;
+	int result;
+
+#ifndef STATSDIR
+#define STATSDIR	"/var/tmp/visservers"
+#endif
+#define STATSFILE	STATSDIR "/" "data.xml"
+	if (access(STATSDIR, X_OK) != 0) {
+	    mkdir(STATSDIR, 0770);
+	}
+	length = Tcl_DStringLength(&ds);
+	f = open(STATSFILE, O_APPEND | O_CREAT | O_WRONLY, 0600);
+	result = FALSE;
+	if (f < 0) {
+	    goto error;
+	}
+	if (write(f, Tcl_DStringValue(&ds), length) != length) {
+	    goto error;
+	}
+	result = TRUE;
+ error:
+	if (f >= 0) {
+	    close(f);
+	}
+	Tcl_DStringFree(&ds);
+	return result;
+    }
+}
+#endif
+
+static void
+DoExit(int code)
+{
+#if KEEPSTATS
+    WriteStats("pymolproxy", code);
+#endif
+    
+    exit(code);
+}
+
+static int
+ExecuteCommand(Tcl_Interp *interp, const char *cmd) 
+{
+    struct timeval tv;
+    double start, finish;
+    int result;
+
+    gettimeofday(&tv, NULL);
+    start = CVT2SECS(tv);
+
+    Debug("command from client is (%s)", cmd);
+    result = Tcl_Eval(interp, cmd);
+
+    gettimeofday(&tv, NULL);
+    finish = CVT2SECS(tv);
+
+    stats.cmdTime += finish - start;
+    stats.numCommands++;
+    return result;
+}
+
+static Image *
+NewImage(PymolProxy *proxyPtr, size_t dataLength)
+{
+    Image *imgPtr;
+
+    imgPtr = malloc(sizeof(Image) + dataLength);
+    if (imgPtr == NULL) {
+	ERROR("can't allocate image of %lu bytes", 
+	      (unsigned long)(sizeof(Image) + dataLength));
+	abort();
+    }
+    imgPtr->prevPtr = imgPtr->nextPtr = NULL;
+    imgPtr->bytesLeft = dataLength;
+    if (proxyPtr->headPtr != NULL) {
+	proxyPtr->headPtr->prevPtr = imgPtr;
+    }
+    imgPtr->nextPtr = proxyPtr->headPtr;
+    if (proxyPtr->tailPtr == NULL) {
+	proxyPtr->tailPtr = imgPtr;
+    }
+    proxyPtr->headPtr = imgPtr;
+    imgPtr->numWritten = 0;
+    return imgPtr;
+}
+
+INLINE static void
+FreeImage(Image *imgPtr)
+{
+    assert(imgPtr != NULL);
+    free(imgPtr);
+}
+
+static void
+WriteImage(PymolProxy *proxyPtr, int fd)
+{
+    Image *imgPtr, *prevPtr; 
+
+    if (proxyPtr->tailPtr == NULL) {
+	ERROR("Should not be here: no image available to write");
+	return;
+    }
+    for (imgPtr = proxyPtr->tailPtr; imgPtr != NULL; imgPtr = prevPtr) {
+	ssize_t bytesLeft;
+
+	assert(imgPtr->nextPtr == NULL);
+	prevPtr = imgPtr->prevPtr;
+#if WRITETRACE
+	Debug("WriteImage: want to write %d bytes.", imgPtr->bytesLeft);
+#endif
+	for (bytesLeft = imgPtr->bytesLeft; bytesLeft > 0; /*empty*/) {
+	    ssize_t numWritten;
+#if WRITETRACE
+	    Debug("WriteImage: try to write %d bytes.", bytesLeft);
+#endif
+	    numWritten = write(fd, imgPtr->data + imgPtr->numWritten, bytesLeft);
+#if WRITETRACE
+	    Debug("WriteImage: wrote %d bytes.", numWritten);
+#endif
+	    if (numWritten < 0) {
+	        ERROR("Error writing fd=%d, %s", fd, strerror(errno));
+		return;
+	    }
+	    bytesLeft -= numWritten;
+	    if (bytesLeft > 0) {
+		/* Wrote a short buffer, means we would block. */
+		imgPtr->numWritten += numWritten;
+		imgPtr->bytesLeft = bytesLeft;
+		return;
+	    }
+	    imgPtr->numWritten += numWritten;
+	}
+	/* Check if image is on the head.  */
+	proxyPtr->tailPtr = prevPtr;
+	if (prevPtr != NULL) {
+	    prevPtr->nextPtr = NULL;
+	}
+	FreeImage(imgPtr);
+    }
+    proxyPtr->headPtr = NULL;
+}
+
+static int
+Pymol(PymolProxy *proxyPtr, const char *format, ...)
+{
+    va_list ap;
+    char buffer[BUFSIZ];
+    char expect[BUFSIZ];
+    int result;
+    ssize_t numWritten;
+    size_t length;
+    char *p;
+
+    if (proxyPtr->error) {
+        return TCL_ERROR;
+    }
+    
+    va_start(ap, format);
+    result = vsnprintf(buffer, BUFSIZ-1, format, ap);
+    va_end(ap);
+    
+    Debug("to-pymol>(%s) code=%d", buffer, result);
+    script("%s\n", buffer);
+    
+    
+    /* Write the command out to the server. */
+    length = strlen(buffer);
+    numWritten = write(proxyPtr->sin, buffer, length);
+    if (numWritten != length) {
+	ERROR("short write to pymol (wrote=%d, should have been %d): %s",
+	      numWritten, length, strerror(errno));
+    }
+    for (p = buffer; *p != '\0'; p++) {
+	if (*p == '\n') {
+	    *p = '\0';
+	    break;
+	}
+    }
+    sprintf(expect, "PyMOL>%s", buffer);
+    /* Now wait for the "PyMOL>" prompt. */
+    if (Expect(proxyPtr, expect, buffer, BUFSIZ) != TCL_OK) {
+	proxyPtr->error = 1;
+	proxyPtr->status = TCL_ERROR;
+	return TCL_ERROR;
+    }
+    return  proxyPtr->status;
+}
+
+static void
+SetViewport(PymolProxy *proxyPtr)
+{
+    if (proxyPtr->flags & VIEWPORT_PENDING) {
+	Pymol(proxyPtr, "viewport %d,%d\n", proxyPtr->width, proxyPtr->height);
+	proxyPtr->flags &= ~VIEWPORT_PENDING;
+    }
+}
+
+static void
+SetZoom(PymolProxy *proxyPtr)
+{
+    if (proxyPtr->flags & ZOOM_PENDING) {
+        Pymol(proxyPtr,"move z,%f\n", proxyPtr->zoom);
+	proxyPtr->flags &= ~ZOOM_PENDING;
+    }
+}
+
+static void
+SetPan(PymolProxy *proxyPtr)
+{
+    if (proxyPtr->flags & PAN_PENDING) {
+	Pymol(proxyPtr,"move x,%f\nmove y,%f\n", proxyPtr->xPan,proxyPtr->yPan);
+	proxyPtr->flags &= ~PAN_PENDING;
+    }
+}
+
+static void
+SetRotation(PymolProxy *proxyPtr)
+{
+    if (proxyPtr->flags & ROTATE_PENDING) {
+	/* Every pymol command line generates a new rendering. Execute all
+	 * three turns as a single command line. */
+	Pymol(proxyPtr,"turn x,%f\nturn y,%f\nturn z,%f\n", 
+	      proxyPtr->xAngle, proxyPtr->yAngle, proxyPtr->zAngle);
+	proxyPtr->xAngle = proxyPtr->yAngle = proxyPtr->zAngle = 0.0f;
+	proxyPtr->flags &= ~ROTATE_PENDING;
+    }
+}
+
+static void
+SetSphereScale(PymolProxy *proxyPtr)
+{
+    if (proxyPtr->flags & ATOM_SCALE_PENDING) {
+	Pymol(proxyPtr, "set sphere_scale,%f,all\n", proxyPtr->sphereScale);
+	proxyPtr->flags &= ~ATOM_SCALE_PENDING;
+    }
+}
+
+static void
+SetStickRadius(PymolProxy *proxyPtr)
+{
+    if (proxyPtr->flags & STICK_RADIUS_PENDING) {
+	Pymol(proxyPtr, "set stick_radius,%f,all\n", proxyPtr->stickRadius);
+	proxyPtr->flags &= ~STICK_RADIUS_PENDING;
+    }
+}
+
+static void
+UpdateSettings(PymolProxy *proxyPtr)
+{
+    /* Handle all the pending setting changes now. */
+    if (proxyPtr->flags & VIEWPORT_PENDING) {
+	SetViewport(proxyPtr);
+    }
+    if (proxyPtr->flags & ROTATE_PENDING) {
+	SetRotation(proxyPtr);
+    }
+    if (proxyPtr->flags & PAN_PENDING) {
+	SetPan(proxyPtr);
+    }
+    if (proxyPtr->flags & ZOOM_PENDING) {
+	SetZoom(proxyPtr);
+    }
+    if (proxyPtr->flags & ATOM_SCALE_PENDING) {
+	SetSphereScale(proxyPtr);
+    }
+    if (proxyPtr->flags & STICK_RADIUS_PENDING) {
+	SetStickRadius(proxyPtr);
+    }
+}
+
+static int
+CartoonCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	   const char *argv[])
+{
+    int bool, defer, push, i;
+    PymolProxy *proxyPtr = clientData;
+    const char *model;
+
+    clear_error(proxyPtr);
+    defer = push = FALSE;
+    model = "all";
+    bool = FALSE;
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0) {
+            defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = TRUE;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc) {
+                model = argv[i];
+	    }
+	} else { 
+	    if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	}
+    }
+    proxyPtr->flags |= INVALIDATE_CACHE; /* cartoon */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    if (bool) {
+	Pymol(proxyPtr, "show cartoon,%s\n", model);
+    } else {
+	Pymol(proxyPtr, "hide cartoon,%s\n", model);
+    }
+    return proxyPtr->status;
+}
+
+static int
+CartoonTraceCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+		const char *argv[])
+{
+    int bool, defer, push, i;
+    PymolProxy *proxyPtr = clientData;
+    const char *model;
+
+    clear_error(proxyPtr);
+    defer = push = FALSE;
+    bool = FALSE;
+    model = "all";
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0) {
+            defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = TRUE;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc) {
+                model = argv[i];
+	    }
+	} else { 
+	    if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	}
+    }
+    proxyPtr->flags |= INVALIDATE_CACHE; /* cartoon_trace  */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    Pymol(proxyPtr, "set cartoon_trace,%d,%s\n", bool, model);
+    return proxyPtr->status;
+}
+
+static int
+DisableCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	   const char *argv[])
+{
+    PymolProxy *proxyPtr = clientData;
+    const char *model = "all";
+    int i, defer = 0, push = 0;
+
+    clear_error(proxyPtr);
+
+    for(i = 1; i < argc; i++) {
+
+        if (strcmp(argv[i], "-defer") == 0 )
+            defer = 1;
+        else if (strcmp(argv[i], "-push") == 0 )
+            push = 1;
+        else
+            model = argv[i];
+        
+    }
+
+    proxyPtr->flags |= INVALIDATE_CACHE;  /* disable */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+
+    Pymol( proxyPtr, "disable %s\n", model);
+
+    return proxyPtr->status;
+}
+
+
+static int
+EnableCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	  const char *argv[])
+{
+    PymolProxy *proxyPtr = clientData;
+    const char *model;
+    int i, defer, push;
+
+    clear_error(proxyPtr);
+    push = defer = FALSE;
+    model = "all";
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0) {
+            defer = TRUE;
+	} else if (strcmp(argv[i], "-push") == 0) {
+            push = TRUE;
+	} else {
+            model = argv[i];
+	}
+    }
+    proxyPtr->flags |= INVALIDATE_CACHE; /* enable */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    Pymol( proxyPtr, "enable %s\n", model);
+    return proxyPtr->status;
+}
+
+static int
+FrameCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	 const char *argv[])
+{
+    PymolProxy *proxyPtr = clientData;
+    int i, push, defer, frame;
+
+    clear_error(proxyPtr);
+    frame = 0;
+    push = defer = TRUE;
+    for(i = 1; i < argc; i++) { 
+        if (strcmp(argv[i],"-defer") == 0) {
+            defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = TRUE;
+	} else {
+            frame = atoi(argv[i]);
+	}
+    }
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    proxyPtr->frame = frame;
+
+    /* Does not invalidate cache? */
+
+    Pymol(proxyPtr,"frame %d\n", frame);
+    return proxyPtr->status;
+}
+
+
+static int
+LabelCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	 const char *argv[])
+{
+    PymolProxy *proxyPtr = clientData;
+    int i, push, defer, state, size;
+    const char *model;
+
+    clear_error(proxyPtr);
+    model = "all";
+    size = 14;
+    state = TRUE;
+    push = defer = FALSE;
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0) {
+            defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = TRUE;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc) {
+                model = argv[i];
+	    }
+	} else if (strcmp(argv[i],"-size") == 0) {
+            if (++i < argc) {
+                size = atoi(argv[i]);
+	    }
+	} else if (Tcl_GetBoolean(interp, argv[i], &state) != TCL_OK) {
+	    return TCL_ERROR;
+	}
+    }
+    proxyPtr->flags |= INVALIDATE_CACHE;  /* label */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    Pymol(proxyPtr, 
+	  "set label_color,white,%s\n"
+	  "set label_size,%d,%s\n", 
+	  model, size, model);
+    if (state) {
+        Pymol(proxyPtr, "label %s,\"%%s%%s\" %% (ID,name)\n", model);
+    } else {
+        Pymol(proxyPtr, "label %s\n", model);
+    }
+    return proxyPtr->status;
+}
+
+/* 
+ * LoadPDBCmd --
+ *
+ *	Load a PDB file into pymol.  There is no good way to load PDB data
+ *	into pymol without using a file.  This causes problems in that we
+ *	don't know when pymol is done with the file.  So there's always
+ *	a bit of mess left around. We use the same file for every file
+ *	so there's a chance that pymol is still using a file while we're
+ *	overwriting it.  
+ *
+ *	The command expects the number of bytes in the pdb data, the 
+ *	name, and the state.
+ */
+static int
+LoadPDBCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	   const char *argv[])
+{
+    const char *data, *name;
+    char *allocated;
+    PymolProxy *proxyPtr = clientData;
+    int state, defer, push;
+    int numBytes;
+    int i, j;
+
+    if (proxyPtr == NULL){
+	return TCL_ERROR;
+    }
+    clear_error(proxyPtr);
+    defer = push = FALSE;
+    for(i = j = 1; i < argc; i++) {
+	if (strcmp(argv[i],"-defer") == 0) {
+	    defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+	    push = TRUE;
+	} else {
+	    if (j < i) {
+		argv[j] = argv[i];
+	    }
+	    j++;
+	}
+    }
+    argc = j;
+    if (argc < 4) {
+	Tcl_AppendResult(interp, "wrong # arguments: should be \"", argv[0],
+			 " <data>|follows <model> <state> ?<numBytes>?\"",
+			 (char *)NULL);
+	return TCL_ERROR;
+    }
+    data = argv[1];
+    name = argv[2];
+    if (Tcl_GetInt(interp, argv[3], &state) != TCL_OK) {
+	return TCL_ERROR;
+    }
+    numBytes = -1;
+    if (strcmp(data, "follows") == 0) {
+	if (argc != 5) {
+	    Tcl_AppendResult(interp, "wrong # arguments: should be \"", argv[0],
+			 " follows <model> <state> <numBytes>\"", (char *)NULL);
+	    return TCL_ERROR;
+	}
+	if (Tcl_GetInt(interp, argv[4], &numBytes) != TCL_OK) {
+	    return TCL_ERROR;
+	}
+	if (numBytes < 0) {
+	    Tcl_AppendResult(interp, "bad value for # bytes \"", argv[4],
+			 "\"", (char *)NULL);
+	    return TCL_ERROR;
+	}
+    }
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+
+    /* Does not invalidate cache? */
+
+    allocated = NULL;
+    if (numBytes >= 0) {
+	allocated = malloc(sizeof(char) * numBytes);
+	if (allocated == NULL) {
+	    Tcl_AppendResult(interp, "can't allocate buffer for pdbdata.",
+			     (char *)NULL);
+	    return TCL_ERROR;
+	}
+	if (ReadFollowingData(&proxyPtr->client, allocated, numBytes) 
+	    != BUFFER_OK) {
+	    Tcl_AppendResult(interp, "can't read pdbdata from client.",
+			     (char *)NULL);
+	    free(allocated);
+	    return TCL_ERROR;
+	}
+	data = allocated;
+    } else {
+	numBytes = strlen(data);
+    }
+    {
+	int f;
+	ssize_t numWritten;
+	char fileName[200];
+
+	strcpy(fileName, "/tmp/pdb.XXXXXX");
+	proxyPtr->status = TCL_ERROR;
+	f = mkstemp(fileName);
+	if (f < 0) {
+	    Tcl_AppendResult(interp, "can't create temporary file \"", 
+		fileName, "\":", Tcl_PosixError(interp), (char *)NULL);
+	    goto error;
+	} 
+	numWritten = write(f, data, numBytes);
+	if (numBytes != numWritten) {
+	    Tcl_AppendResult(interp, "can't write PDB data to \"",
+			     fileName, "\": ", Tcl_PosixError(interp),
+			     (char *)NULL);
+	    close(f);
+	    goto error;
+	}
+	close(f);
+	Pymol(proxyPtr, "loadandremovepdbfile %s,%s,%d\n", fileName, name, state);
+	proxyPtr->status = TCL_OK;
+    }
+ error:
+    if (allocated != NULL) {
+	free(allocated);
+    }
+    return proxyPtr->status;
+}
+
+static int
+OrthoscopicCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	      const char *argv[])
+{
+    int bool, defer, push, i;
+    PymolProxy *proxyPtr = clientData;
+
+    clear_error(proxyPtr);
+    defer = push = FALSE;
+    bool = FALSE;
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0) {
+            defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = TRUE;
+	} else { 
+	    if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	}
+    }
+    proxyPtr->flags |= INVALIDATE_CACHE; /* orthoscopic */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    Pymol(proxyPtr, "set orthoscopic=%d\n", bool);
+    return proxyPtr->status;
+}
+
+/* 
+ * PanCmd --
+ *
+ *	Issue "move" commands for changes in the x and y coordinates of the
+ *	camera.  The problem here is that there is no event compression.
+ *	Consecutive "pan" commands are not compressed into a single
+ *	directive.  The means that the pymol server will render scenes that
+ *	are not used by the client.
+ *
+ *	Need to 1) defer the "move" commands until we find the next command
+ *	isn't a "pan". 2) Track the x and y coordinates as they are
+ *	compressed.
+ */
+static int
+PanCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	const char *argv[])
+{
+    PymolProxy *proxyPtr = clientData;
+    double x, y;
+    int i;
+    int defer, push;
+
+    clear_error(proxyPtr);
+    defer = push = FALSE;
+    for (i = 1; i < argc; i++) {
+	if (strcmp(argv[i],"-defer") == 0) {
+	    defer = 1;
+	} else if (strcmp(argv[i],"-push") == 0) {
+	    push = 1;
+	} else { 
+	    break;
+	}
+    }
+    if ((Tcl_GetDouble(interp, argv[i], &x) != TCL_OK) ||
+	(Tcl_GetDouble(interp, argv[i+1], &y) != TCL_OK)) {
+	return TCL_ERROR;
+    }
+    proxyPtr->flags |= INVALIDATE_CACHE; /* pan */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    if ((x != 0.0f) || (y != 0.0f)) {
+	proxyPtr->xPan = x * 0.05;
+	proxyPtr->yPan = -y * 0.05;
+	proxyPtr->flags |= PAN_PENDING;
+    }
+    return proxyPtr->status;
+}
+
+static int
+PngCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
+{
+    char buffer[BUFSIZ];
+    int numBytes=0;
+    PymolProxy *proxyPtr = clientData;
+    size_t length;
+    Image *imgPtr;
+
+    clear_error(proxyPtr);
+
+    /* Force pymol to update the current scene. */
+    Pymol(proxyPtr, "refresh\n");
+    Pymol(proxyPtr, "png -\n");
+
+    if (Expect(proxyPtr, "png image follows: ", buffer, BUFSIZ-1) != TCL_OK) {
+	return TCL_ERROR;
+    }
+    if (sscanf(buffer, "png image follows: %d\n", &numBytes) != 1) {
+	Tcl_AppendResult(interp, "can't get # bytes from \"", buffer, "\"",
+			 (char *)NULL);
+	return TCL_ERROR;
+    } 
+    sprintf(buffer, "nv>image %d %d %d %d\n", numBytes, proxyPtr->cacheId, 
+	    proxyPtr->frame, proxyPtr->rockOffset);
+    length = strlen(buffer);
+    imgPtr = NewImage(proxyPtr, numBytes + length);
+    strcpy(imgPtr->data, buffer);
+    if (ReadFollowingData(&proxyPtr->server, imgPtr->data + length, numBytes)
+	!= BUFFER_OK) {
+        ERROR("can't read %d bytes for \"image follows\" buffer: %s", numBytes, 
+	      strerror(errno));
+	return  TCL_ERROR;
+    }
+    if (Expect(proxyPtr, " ScenePNG", buffer, BUFSIZ-1) != TCL_OK) {
+	return TCL_ERROR;
+    }
+    stats.numFrames++;
+    stats.numBytes += numBytes;
+    return proxyPtr->status;
+}
+
+
+static int
+PpmCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
+{
+    char buffer[BUFSIZ];
+    int numBytes=0;
+    PymolProxy *proxyPtr = clientData;
+    size_t length;
+    Image *imgPtr;
+
+    clear_error(proxyPtr);
+
+    /* Force pymol to update the current scene. */
+    Pymol(proxyPtr, "refresh\n");
+    Pymol(proxyPtr, "png -,format=1\n");
+
+    if (Expect(proxyPtr, "ppm image follows: ", buffer, BUFSIZ-1) != TCL_OK) {
+	return TCL_ERROR;
+    }
+    if (sscanf(buffer, "ppm image follows: %d\n", &numBytes) != 1) {
+	Tcl_AppendResult(interp, "can't get # bytes from \"", buffer, "\"",
+			 (char *)NULL);
+	return TCL_ERROR;
+    } 
+    sprintf(buffer, "nv>image %d %d %d %d\n", numBytes, proxyPtr->cacheId, 
+	    proxyPtr->frame, proxyPtr->rockOffset);
+    length = strlen(buffer);
+    imgPtr = NewImage(proxyPtr, numBytes + length);
+    strcpy(imgPtr->data, buffer);
+    if (ReadFollowingData(&proxyPtr->server, imgPtr->data + length, numBytes)
+	!= BUFFER_OK) {
+        ERROR("can't read %d bytes for \"image follows\" buffer: %s", numBytes, 
+	      strerror(errno));
+	return  TCL_ERROR;
+    }
+#ifdef notdef
+    if (Expect(proxyPtr, " ScenePNG", buffer, BUFSIZ-1) != TCL_OK) {
+	return TCL_ERROR;
+    }
+#endif
+    stats.numFrames++;
+    stats.numBytes += numBytes;
+    return proxyPtr->status;
+}
+
+
+static int
+PrintCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	 const char *argv[])
+{
+    char buffer[800];
+    int numBytes=0;
+    PymolProxy *proxyPtr = clientData;
+    size_t length;
+    Image *imgPtr;
+    int width, height;
+    const char *token, *bgcolor;
+
+    clear_error(proxyPtr);
+
+    if (argc != 5) {
+	Tcl_AppendResult(interp, "wrong # arguments: should be \"", 
+			 argv[0], " token width height color\"", (char *)NULL);
+	return TCL_ERROR;
+    }
+    token = argv[1];
+    if (Tcl_GetInt(interp, argv[2], &width) != TCL_OK) {
+	return TCL_ERROR;
+    }
+    if (Tcl_GetInt(interp, argv[3], &height) != TCL_OK) {
+	return TCL_ERROR;
+    }
+    bgcolor = argv[4];
+    /* Force pymol to update the current scene. */
+    if (strcmp(bgcolor, "none") == 0) {
+	Pymol(proxyPtr, "set ray_opaque_background,off\n");
+	Pymol(proxyPtr, "refresh\n", bgcolor);
+    } else {
+	Pymol(proxyPtr, "set ray_opaque_background,on\n");
+	Pymol(proxyPtr, "bg_color %s\nrefresh\n", bgcolor);
+    }
+    Pymol(proxyPtr, "ray %d,%d\n", width, height);
+    if (Expect(proxyPtr, " Ray:", buffer, BUFSIZ-1) != TCL_OK) {
+	return TCL_ERROR;
+    }
+
+    Pymol(proxyPtr, "png -,dpi=300\nbg_color black\n");
+    if (Expect(proxyPtr, "png image follows: ", buffer, BUFSIZ-1) != TCL_OK) {
+	return TCL_ERROR;
+    }
+
+    if (sscanf(buffer, "png image follows: %d\n", &numBytes) != 1) {
+	Tcl_AppendResult(interp, "can't get # bytes from \"", buffer, "\"",
+			 (char *)NULL);
+	return TCL_ERROR;
+    }
+    sprintf(buffer, "nv>image %d print \"%s\" %d\n", numBytes, token, 
+	    proxyPtr->rockOffset);
+    Debug("header is png is (%s)\n", buffer);
+    length = strlen(buffer);
+    imgPtr = NewImage(proxyPtr, numBytes + length);
+    strcpy(imgPtr->data, buffer);
+    if (ReadFollowingData(&proxyPtr->server, imgPtr->data + length, numBytes)
+	!= BUFFER_OK) {
+        ERROR("can't read %d bytes for \"image follows\" buffer: %s", numBytes,
+	      strerror(errno));
+	return  TCL_ERROR;
+    }
+    if (Expect(proxyPtr, " ScenePNG", buffer, BUFSIZ-1) != TCL_OK) {
+	return TCL_ERROR;
+    }
+
+    stats.numFrames++;
+    stats.numBytes += numBytes;
+    return proxyPtr->status;
+}
+
+static int
+RawCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
+{
+    PymolProxy *proxyPtr = clientData;
+    int arg, defer = 0, push = 0;
+    const char *cmd;
+    clear_error(proxyPtr);
+
+    cmd = NULL;
+    for(arg = 1; arg < argc; arg++) {
+        if (strcmp(argv[arg], "-defer") == 0)
+            defer = 1;
+        else if (strcmp(argv[arg], "-push") == 0)
+            push = 1;
+        else {
+            cmd = argv[arg];
+        }
+    }
+
+    proxyPtr->flags |= INVALIDATE_CACHE; /* raw */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    Pymol(proxyPtr,"%s\n", cmd);
+    return proxyPtr->status;
+}
+
+static int
+ResetCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	 const char *argv[])
+{
+    PymolProxy *proxyPtr = clientData;
+    int arg, push = 0, defer = 0;
+
+    clear_error(proxyPtr);
+
+    for(arg = 1; arg < argc; arg++) {
+        if ( strcmp(argv[arg],"-defer") == 0 )
+            defer = 1;
+        else if (strcmp(argv[arg],"-push") == 0 )
+            push = 1;
+    }
+                
+    proxyPtr->flags |= INVALIDATE_CACHE; /* reset */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    Pymol(proxyPtr, 
+	  "reset\n"
+	  "zoom complete=1\n");
+    return proxyPtr->status;
+}
+
+static int
+RockCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	const char *argv[])
+{
+    PymolProxy *proxyPtr = clientData;
+    float y = 0.0;
+    int arg, push = 0, defer = 0;
+
+    clear_error(proxyPtr);
+
+    for(arg = 1; arg < argc; arg++) {
+        if ( strcmp(argv[arg],"-defer") == 0 )
+            defer = 1;
+        else if (strcmp(argv[arg],"-push") == 0 )
+            push = 1;
+        else
+            y = atof( argv[arg] );
+    }
+                
+    /* Does not invalidate cache. */
+
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    Pymol(proxyPtr,"turn y, %f\n", y - proxyPtr->rockOffset);
+    proxyPtr->rockOffset = y;
+    return proxyPtr->status;
+}
+
+static int
+RepresentationCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+		  const char *argv[])
+{
+    PymolProxy *proxyPtr = clientData;
+    const char *model;
+    const char *rep;
+    int defer, push, i;
+
+    clear_error(proxyPtr);
+    defer = push = FALSE;
+    model = "all";
+    rep = NULL;
+    for (i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0 ) {
+            defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = TRUE;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc) {
+                model = argv[i];
+	    }
+        } else {
+            rep = argv[i];
+	}
+    }
+    if (rep == NULL) {
+	Tcl_AppendResult(interp, "missing representation argument",
+			 (char *)NULL);
+	return TCL_ERROR;
+    }
+
+    proxyPtr->flags |= INVALIDATE_CACHE; /* representation */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    if (strcmp(rep, "ballnstick") == 0) { /* Ball 'n Stick */
+	Pymol(proxyPtr, 
+	      "set stick_color,white,%s\n"
+	      "show sticks,%s\n"
+	      "show spheres,%s\n"
+	      "hide lines,%s\n"
+	      "hide cartoon,%s\n",
+	      model, model, model, model, model);
+    } else if (strcmp(rep, "spheres") == 0) { /* spheres */    
+	Pymol(proxyPtr, 
+	      "hide sticks,%s\n"
+	      "show spheres,%s\n"
+	      "hide lines,%s\n"
+	      "hide cartoon,%s\n"
+	      "set sphere_quality,2,%s\n"
+	      "set ambient,.2,%s\n", 
+	      model, model, model, model, model, model);
+    } else if (strcmp(rep, "none") == 0) { /* nothing */    
+	Pymol(proxyPtr, 
+	      "hide sticks,%s\n", 
+	      "hide spheres,%s\n"
+	      "hide lines,%s\n"
+	      "hide cartoon,%s\n",
+	      model, model, model, model);
+    } else if (strcmp(rep, "sticks") == 0) { /* sticks */    
+	Pymol(proxyPtr, 
+	      "set stick_color,white,%s\n"
+	      "show sticks,%s\n"
+	      "hide spheres,%s\n"
+	      "hide lines,%s\n"
+	      "hide cartoon,%s\n",
+	      model, model, model, model, model);
+    } else if (strcmp(rep, "lines") == 0) { /* lines */    
+	Pymol(proxyPtr, 
+	      "hide sticks,%s\n"
+	      "hide spheres,%s\n"
+	      "show lines,%s\n"
+	      "hide cartoon,%s\n",
+	      model, model, model, model);
+    } else if (strcmp(rep, "cartoon") == 0) { /* cartoon */    
+	Pymol(proxyPtr, 
+	      "hide sticks,%s\n"
+	      "hide spheres,%s\n"
+	      "hide lines,%s\n"
+	      "show cartoon,%s\n", 
+	      model, model, model, model);
+    } 
+    return proxyPtr->status;
+}
+
+/* 
+ * RotateCmd --
+ *
+ *	Issue "turn" commands for changes in the angle of the camera.  The
+ *	problem here is that there is no event compression.  Consecutive
+ *	"rotate" commands are not compressed into a single directive.  The
+ *	means that the pymol server will render many scene that are not used
+ *	by the client.
+ *
+ *	Need to 1) defer the "turn" commands until we find the next command
+ *	isn't a "rotate". 2) Track the rotation angles as they are compressed.
+ */
+static int
+RotateCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	  const char *argv[])
+{
+    float xAngle, yAngle, zAngle;
+    PymolProxy *proxyPtr = clientData;
+    int defer = 0, push = 0, arg, varg = 1;
+
+    clear_error(proxyPtr);
+
+    xAngle = yAngle = zAngle = 0.0f;
+    for(arg = 1; arg < argc; arg++) {
+	if (strcmp(argv[arg],"-defer") == 0) {
+	    defer = 1; 
+	} else if (strcmp(argv[arg],"-push") == 0) {
+	    push = 1;
+	} else  if (varg == 1) {
+	    double value;
+	    if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	    xAngle = (float)value;
+	    varg++;
+	} else if (varg == 2) {
+	    double value;
+	    if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	    yAngle = (float)value;
+	    varg++;
+	} else if (varg == 3) {
+	    double value;
+	    if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	    zAngle = (float)value;
+	    varg++;
+	}
+    } 
+    proxyPtr->flags |= INVALIDATE_CACHE; /* rotate */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    if ((xAngle != 0.0f) || (yAngle != 0.0f) || (zAngle != 0.0f)) {
+	proxyPtr->xAngle += xAngle;
+	proxyPtr->yAngle += yAngle;
+	proxyPtr->zAngle += zAngle;
+	proxyPtr->flags |= ROTATE_PENDING;
+    }
+    return proxyPtr->status;
+}
+
+static int
+ScreenCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	  const char *argv[])
+{
+    PymolProxy *proxyPtr = clientData;
+    int width = -1, height = -1;
+    int defer, push, i, varg;
+
+    clear_error(proxyPtr);
+    defer = push = FALSE;
+    varg = 1;
+    for(i = 1; i < argc; i++) {
+        if ( strcmp(argv[i],"-defer") == 0 ) 
+            defer = 1;
+        else if ( strcmp(argv[i], "-push") == 0 )
+            push = 1;
+        else if (varg == 1) {
+            width = atoi(argv[i]);
+            height = width;
+            varg++;
+        }
+        else if (varg == 2) {
+            height = atoi(argv[i]);
+            varg++;
+        }
+    }
+    if ((width < 0) || (height < 0)) {
+	return TCL_ERROR;
+    }
+    proxyPtr->flags |= INVALIDATE_CACHE; /* viewport */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    proxyPtr->width = width;
+    proxyPtr->height = height;
+    proxyPtr->flags |= VIEWPORT_PENDING;
+
+    //usleep(205000); // .2s delay for pymol to update its geometry *HACK ALERT*
+        
+    return proxyPtr->status;
+}
+
+static int
+SphereScaleCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	   const char *argv[])
+{
+    int defer = 0, push = 0, i;
+    double scale;
+    const char *model = "all";
+    PymolProxy *proxyPtr = clientData;
+
+    clear_error(proxyPtr);
+    scale = 0.25f;
+    for(i = 1; i < argc; i++) {
+        if ( strcmp(argv[i],"-defer") == 0 ) {
+            defer = 1;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = 1;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc) {
+                model = argv[i];
+	    }
+        } else {
+	    if (Tcl_GetDouble(interp, argv[i], &scale) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	}
+    }
+    proxyPtr->flags |= INVALIDATE_CACHE;  /* sphere_scale */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+
+    if (strcmp(model, "all") == 0) {
+	proxyPtr->flags |= ATOM_SCALE_PENDING;
+	proxyPtr->sphereScale = scale;
+    } else {
+	Pymol(proxyPtr, "set sphere_scale,%f,%s\n", scale, model);
+    }
+    return proxyPtr->status;
+}
+
+static int
+StickRadiusCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+		 const char *argv[])
+{
+    int defer = 0, push = 0, i;
+    double scale;
+    const char *model = "all";
+    PymolProxy *proxyPtr = clientData;
+
+    clear_error(proxyPtr);
+    scale = 0.25f;
+    for(i = 1; i < argc; i++) {
+        if ( strcmp(argv[i],"-defer") == 0 ) {
+            defer = 1;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = 1;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc)
+                model = argv[i];
+        } else {
+	    if (Tcl_GetDouble(interp, argv[i], &scale) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	}
+    }
+    proxyPtr->flags |= INVALIDATE_CACHE;  /* stick_radius */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+
+    if (strcmp(model, "all") == 0) {
+	proxyPtr->flags |= STICK_RADIUS_PENDING;
+	proxyPtr->stickRadius = scale;
+    } else {
+	Pymol(proxyPtr, "set stick_radius,%f,%s\n", scale, model);
+    }
+    return proxyPtr->status;
+}
+
+static int
+TransparencyCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+		const char *argv[])
+{
+    PymolProxy *proxyPtr = clientData;
+    const char *model;
+    float transparency;
+    int defer, push;
+    int i;
+
+    clear_error(proxyPtr);
+    model = "all";
+    defer = push = FALSE;
+    transparency = 0.0f;
+    for(i = 1; i < argc; i++) {
+        if ( strcmp(argv[i],"-defer") == 0 ) {
+            defer = 1;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = 1;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc) {
+                model = argv[i];
+	    }
+	} else {
+	    transparency = atof(argv[i]);
+	}
+    }
+    proxyPtr->flags |= INVALIDATE_CACHE; /* transparency */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    } 
+    Pymol(proxyPtr, 
+	  "set sphere_transparency,%g,%s\n"
+	  "set stick_transparency,%g,%s\n"
+	  "set cartoon_transparency,%g,%s\n",
+	  transparency, model, transparency, model, 
+	  transparency, model);
+    return proxyPtr->status;
+}
+
+static int
+VMouseCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	  const char *argv[])
+{
+    PymolProxy *proxyPtr = clientData;
+    int i, defer = 0, push = 0, varg = 1;
+    int arg1 = 0, arg2 = 0, arg3 = 0, arg4 = 0, arg5 = 0;
+
+    clear_error(proxyPtr);
+
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i], "-defer") == 0)
+            defer = 1;
+        else if (strcmp(argv[i], "-push") == 0)
+            push = 1;
+        else if (varg == 1) {
+            arg1 = atoi(argv[i]);
+            varg++;
+        }
+        else if (varg == 2) {
+            arg2 = atoi(argv[i]);
+            varg++;
+        }
+        else if (varg == 3) {
+            arg3 = atoi(argv[i]);
+            varg++;
+        }
+        else if (varg == 4) {
+            arg4 = atoi(argv[i]);
+            varg++;
+        }
+        else if (varg == 5) {
+            arg5 = atoi(argv[i]);
+            varg++;
+        }
+    }
+
+    proxyPtr->flags |= INVALIDATE_CACHE; /* vmouse */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    Pymol(proxyPtr, "vmouse %d,%d,%d,%d,%d\n", arg1, arg2, arg3, arg4, arg5);
+    return proxyPtr->status;
+}
+
+
+/* 
+ * ZoomCmd --
+ *
+ *	Issue "move" commands for changes in the z-coordinate of the camera.
+ *	The problem here is that there is no event compression.  Consecutive
+ *	"zoom" commands are not compressed into a single directive.  The means
+ *	that the pymol server will render scenes that are not used by the
+ *	client.
+ *
+ *	Need to 1) defer the "move" commands until we find the next command
+ *	isn't a "zoom". 2) Track the z-coordinate as they are compressed.
+ */
+static int
+ZoomCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
+{
+    double factor = 0.0;
+    PymolProxy *proxyPtr = clientData;
+    int defer = 0, push = 0, i, varg = 1;
+
+    clear_error(proxyPtr);
+
+    for(i = 1; i < argc; i++) {
+	if (strcmp(argv[i],"-defer") == 0)
+	    defer = 1;
+	else if (strcmp(argv[i],"-push") == 0)
+	    push = 1;
+	else if (varg == 1) {
+	    double value;
+	    if (Tcl_GetDouble(interp, argv[i], &value) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	    factor = (float)value;
+	    varg++;
+	}
+    }
+    proxyPtr->flags |= INVALIDATE_CACHE; /* Zoom */
+    if (!defer || push) {
+	proxyPtr->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	proxyPtr->flags |= FORCE_UPDATE;
+    }
+    if (factor != 0.0) {
+	proxyPtr->zoom = factor;
+	proxyPtr->flags |= ZOOM_PENDING;
+    }
+    return proxyPtr->status;
+}
+
+        
+static void
+ChildHandler(int sigNum) 
+{
+    ERROR("pymol (%d) died unexpectedly", stats.child);
+    exit(1);
+}
+
+static int 
+ProxyInit(int cin, int cout, char *const *argv)
+{
+    char stderrFile[200];
+    int status, result = 0;
+    int sin[2];
+    int sout[2];
+    Tcl_Interp *interp;
+    pid_t child, parent;
+    PymolProxy proxy;
+    struct timeval end;
+
+    /* Create three pipes for communication with the external application. One
+     * each for the applications's: stdin, stdout, and stderr  */
+
+    if (pipe(sin) == -1)
+        return -1;
+
+    if (pipe(sout) == -1) {
+        close(sin[0]);
+        close(sin[1]);
+        return -1;
+    }
+
+    parent = getpid();
+    sprintf(stderrFile, "/tmp/pymol%d.stderr", parent);
+
+    /* Fork the new process.  Connect I/O to the new socket.  */
+
+    child = fork();
+    if (child < 0) {
+        ERROR("can't fork process: %s\n", strerror(errno));
+        return -3;
+    }
+
+    interp = Tcl_CreateInterp();
+    Tcl_MakeSafe(interp);
+
+    if (child == 0) {
+        int f;
+	char path[200];
+
+        /* Child process */
+        
+        /* 
+         * Create a new process group, so we can later kill this process and
+         * all its children without affecting the process that created this
+         * one.
+         */
+        setpgid(child, 0); 
+        
+        /* Redirect stdin, stdout, and stderr to pipes before execing */ 
+
+        dup2(sin[0],  0);		// stdin
+        dup2(sout[1], 1);		// stdout
+	f = open(stderrFile, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+	if (f < 0) {
+	    ERROR("can't open server error file `%s': %s", path, 
+		  strerror(errno));
+	    exit(1);
+	}
+        dup2(f, 2);			/* Redirect stderr to a file */
+        
+	/* Close all other descriptors  */        
+	for (f = 3; f < FD_SETSIZE; f++) {
+            close(f);
+	}
+        INFO("attempting to start \"%s\"", argv[0]);
+        
+        execvp(argv[0], argv);
+        ERROR("can't exec `%s': %s", argv[0], strerror(errno));
+	exit(-1);
+    } else {
+	signal(SIGCHLD, ChildHandler);
+    }
+    stats.child = child;
+
+    Debug("Started %s DISPLAY=%s\n", argv[0], getenv("DISPLAY"));
+
+    /* close opposite end of pipe, these now belong to the child process  */
+    close(sin[0]);
+    close(sout[1]);
+
+    memset(&proxy, 0, sizeof(PymolProxy));
+    proxy.sin  = sin[1];
+    proxy.sout = sout[0];
+    proxy.cin  = cin;
+    proxy.cout = cout;
+    proxy.flags = CAN_UPDATE;
+    proxy.frame = 1;
+    proxy.interp = interp;
+
+    Tcl_CreateCommand(interp, "cartoon",       CartoonCmd,       &proxy, NULL);
+    Tcl_CreateCommand(interp, "cartoontrace",  CartoonTraceCmd,  &proxy, NULL);
+    Tcl_CreateCommand(interp, "disable",       DisableCmd,       &proxy, NULL);
+    Tcl_CreateCommand(interp, "enable",        EnableCmd,        &proxy, NULL);
+    Tcl_CreateCommand(interp, "frame",         FrameCmd,         &proxy, NULL);
+    Tcl_CreateCommand(interp, "label",         LabelCmd,         &proxy, NULL);
+    Tcl_CreateCommand(interp, "loadpdb",       LoadPDBCmd,       &proxy, NULL);
+    Tcl_CreateCommand(interp, "orthoscopic",   OrthoscopicCmd,   &proxy, NULL);
+    Tcl_CreateCommand(interp, "pan",           PanCmd,           &proxy, NULL);
+    Tcl_CreateCommand(interp, "png",           PngCmd,           &proxy, NULL);
+    Tcl_CreateCommand(interp, "ppm",           PpmCmd,           &proxy, NULL);
+    Tcl_CreateCommand(interp, "print",         PrintCmd,         &proxy, NULL);
+    Tcl_CreateCommand(interp, "raw",           RawCmd,           &proxy, NULL);
+    Tcl_CreateCommand(interp, "representation",RepresentationCmd,&proxy, NULL);
+    Tcl_CreateCommand(interp, "reset",         ResetCmd,         &proxy, NULL);
+    Tcl_CreateCommand(interp, "rock",          RockCmd,          &proxy, NULL);
+    Tcl_CreateCommand(interp, "rotate",        RotateCmd,        &proxy, NULL);
+    Tcl_CreateCommand(interp, "screen",        ScreenCmd,        &proxy, NULL);
+    Tcl_CreateCommand(interp, "spherescale",   SphereScaleCmd,   &proxy, NULL);
+    Tcl_CreateCommand(interp, "stickradius",   StickRadiusCmd,   &proxy, NULL);
+    Tcl_CreateCommand(interp, "transparency",  TransparencyCmd,  &proxy, NULL);
+    Tcl_CreateCommand(interp, "viewport",      ScreenCmd,        &proxy, NULL);
+    Tcl_CreateCommand(interp, "vmouse",        VMouseCmd,        &proxy, NULL);
+    Tcl_CreateCommand(interp, "zoom",          ZoomCmd,          &proxy, NULL);
+
+
+    gettimeofday(&end, NULL);
+    stats.start = end;
+
+    // Main Proxy Loop
+    //  accept tcl commands from socket
+    //  translate them into pyMol commands, and issue them to child proccess
+    //  send images back
+
+    PollForEvents(&proxy);
+
+    unlink(stderrFile);
+    close(proxy.cout);
+    close(proxy.sout);
+    close(proxy.cin);
+    close(proxy.sin);
+
+    if (waitpid(child, &result, 0) < 0) {
+        ERROR("error waiting on pymol server to exit: %s", strerror(errno));
+    }
+    INFO("attempting to signal (SIGTERM) pymol server.");
+    kill(-child, SIGTERM);		// Kill process group
+    alarm(5);
+    
+    if (waitpid(child, &result, 0) < 0) {
+        ERROR("error waiting on pymol server to exit after SIGTERM: %s", 
+	      strerror(errno));
+    }
+    status = -1;
+    while ((status == -1) && (errno == EINTR)) {
+	ERROR("Attempting to signal (SIGKILL) pymol server.");
+	kill(-child, SIGKILL);	// Kill process group
+	alarm(10);
+	status = waitpid(child, &result, 0);
+	alarm(0); 
+    }
+    INFO("pymol server process ended (result=%d)", result);
+
+    Tcl_DeleteInterp(interp);
+    
+    if (WIFEXITED(result)) {
+	result = WEXITSTATUS(result);
+    }
+    DoExit(result);
+    return 0;
+}
+
+
+static void
+PollForEvents(PymolProxy *proxyPtr)
+{
+    Tcl_DString clientCmds;
+    struct pollfd pollFd[3];
+    int flags;
+
+    if (write(proxyPtr->cout, "PyMol 1.0\n", 10) != 10) {
+	ERROR("short write of signature");
+    }
+
+    flags = fcntl(proxyPtr->cin, F_GETFL);
+    fcntl(proxyPtr->cin, F_SETFL, flags|O_NONBLOCK);
+
+#ifdef notdef
+    flags = fcntl(proxyPtr->sout, F_GETFL);
+    fcntl(proxyPtr->sout, F_SETFL, flags|O_NONBLOCK);
+
+    flags = fcntl(proxyPtr->cout, F_GETFL);
+    fcntl(proxyPtr->cout, F_SETFL, flags|O_NONBLOCK);
+#endif
+
+    /* Read file descriptors. */
+    pollFd[0].fd = proxyPtr->cout;	/* Client standard output  */
+    pollFd[1].fd = proxyPtr->sout;	/* Server standard error.  */
+    pollFd[0].events = pollFd[1].events = POLLIN;
+
+    /* Write file descriptors. */
+    pollFd[2].fd = proxyPtr->cin;	/* Client standard input. */
+    pollFd[2].events = POLLOUT;
+
+    InitBuffer(&proxyPtr->client, "client", proxyPtr->cout);
+    InitBuffer(&proxyPtr->server, "server", proxyPtr->sout);
+
+    Tcl_DStringInit(&clientCmds);
+    for (;;) {
+	int timeout, nChannels;
+
+	nChannels =  (proxyPtr->headPtr != NULL) ? 3 : 2;
+#define PENDING_TIMEOUT		10  /* milliseconds. */
+	timeout = (proxyPtr->flags & UPDATE_PENDING) ? PENDING_TIMEOUT : -1;
+	nChannels = poll(pollFd, nChannels, timeout);
+	if (nChannels < 0) {
+	    ERROR("POLL ERROR: %s", strerror(errno));
+	    continue;		/* or exit? */
+	}
+
+	/* 
+	 * The next two sections are to drain any leftover data in 
+	 * the pymol server process' stdout or stderr.  We don't want the
+	 * the pymol server to block writing to stderr or stdout.
+	 */
+	if (pollFd[1].revents & POLLIN) { /* Server stdout */
+	    int numBytes;
+	    char *line;
+	    
+	    Debug("Reading pymol stdout\n");
+	    /* Don't care what's in the server output buffer. */
+	    FlushBuffer(&proxyPtr->server);
+	    line = NextLine(&proxyPtr->server, &numBytes);
+	    if (line != NULL) {
+		Debug("STDOUT>%.*s", numBytes, line);
+		INFO("Garbage found from pymol server: %.%s", numBytes, line);
+	    } else if (numBytes == BUFFER_CONTINUE) {
+		Debug("No data found on pymol stdout\n");
+		continue;
+	    } else {
+		ERROR("can't read pymol stdout (numBytes=%d): %s\n", numBytes,
+		      strerror(errno));
+		goto error;		/* Get out on EOF or error. */
+	    }
+	}
+
+	/* We have some descriptors ready. */
+	if (pollFd[0].revents & POLLIN) { /* Client stdout */
+	    Debug("Reading client stdout\n");
+	    for (;;) {
+		int numBytes;
+		char *line;
+		
+		line = NextLine(&proxyPtr->client, &numBytes);
+		if (line != NULL) {
+		    const char *cmd;
+
+		    Tcl_DStringAppend(&clientCmds, line, numBytes);
+		    cmd = Tcl_DStringValue(&clientCmds);
+		    if (Tcl_CommandComplete(cmd)) {
+			int result;
+
+			/* May execute more than one command. */
+			result = ExecuteCommand(proxyPtr->interp, cmd);
+			if (result != TCL_OK) {
+			    Tcl_Obj *objPtr;
+
+			    objPtr = Tcl_GetObjResult(proxyPtr->interp);
+			    ERROR(Tcl_GetString(objPtr));
+			    goto error;
+			}
+			Tcl_DStringSetLength(&clientCmds, 0);
+		    }
+		    continue;
+		}
+		if (numBytes == BUFFER_CONTINUE) {
+		    break;
+		}
+		ERROR("can't read client stdout (numBytes=%d): %s\n", numBytes,
+		      strerror(errno));
+		goto error;		/* Get out on EOF or error. */
+	    }
+	    Debug("done with client stdout\n");
+	}
+	/* 
+	 * Write the currently queued image if there is one.
+	 *
+	 * We want to transmit the current image back to the client.  But if
+	 * the client's busy (e.g. sending us another command), there's a
+	 * chance we'll deadlock.  Therefore, the file descriptor is
+	 * non-blocking and we write only what we can.  Must be careful not to
+	 * refresh the image until we're done.
+	 */
+
+	/* Handle all the pending setting changes now. */
+	UpdateSettings(proxyPtr);
+
+	/* Write the current image buffer. */
+	if (proxyPtr->headPtr == NULL) {
+	    /* We might want to refresh the image if we're not currently
+	     * transmitting an image back to the client. The image will be
+	     * refreshed after the image has been completely transmitted. */
+	    if ((nChannels == 0) || (proxyPtr->flags & FORCE_UPDATE)) {
+		if (proxyPtr->flags & UPDATE_PENDING) {
+		    Tcl_Eval(proxyPtr->interp, "ppm");
+		    proxyPtr->flags &= ~UPDATE_PENDING;
+		}
+		proxyPtr->flags &= ~FORCE_UPDATE;
+		continue;
+	    }
+	}
+	if ((proxyPtr->headPtr != NULL) && (pollFd[2].revents & POLLOUT)) { 
+	    WriteImage(proxyPtr, pollFd[2].fd);
+	}
+    }
+ error:
+    Tcl_DStringFree(&clientCmds);
+    return;
+}
+
+int
+main(int argc, char *argv[])
+{
+    flog = stderr;
+    if (debug) {
+	char fileName[200];
+	sprintf(fileName, "/tmp/pymolproxy%d.log", getpid());
+	sprintf(fileName, "/tmp/pymolproxy.log");
+        flog = fopen(fileName, "w");
+    }    
+    scriptFile = NULL;
+    if (savescript) {
+	char fileName[200];
+	sprintf(fileName, "/tmp/pymolproxy.py");
+        scriptFile = fopen(fileName, "w");
+    }    
+    ProxyInit(fileno(stdout), fileno(stdin), argv + 1);
+    return 0;
+}
+
Index: /pymolproxy/trunk/pymolproxy2.c
===================================================================
--- /pymolproxy/trunk/pymolproxy2.c	(revision 4588)
+++ /pymolproxy/trunk/pymolproxy2.c	(revision 4588)
@@ -0,0 +1,2464 @@
+
+/*
+ * ----------------------------------------------------------------------
+ * proxypymol2.c
+ *
+ *      This module creates the Tcl interface to the pymol server.  It acts as
+ *      a go-between establishing communication between a molvisviewer widget
+ *      and the pymol server. The communication protocol from the molvisviewer
+ *      widget is the Tcl language.  Commands are then relayed to the pymol
+ *      server.  Responses from the pymol server are translated into Tcl
+ *      commands and send to the molvisviewer widget. For example, resulting
+ *      image rendered offscreen is returned as ppm-formatted image data.
+ *
+ *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
+ *
+ *  See the file "license.terms" for information on usage and
+ *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ * ======================================================================
+ */
+
+/*
+ * Notes:   The proxy should not maintain any state information from the
+ *	    client, other that what it needs for event (rotate, pan, zoom,
+ *	    atom scale, bond thickness, etc.) compression.  This is because 
+ *	    the connection is periodically broken (timeout, error, etc.).
+ *	    It's the responsibility of the client (molvisviewer) to restore 
+ *	    the settings of the previous view.  The proxy is just a relay
+ *	    between the client and the pymol server.
+ */
+/*
+ *   +--------------+          +------------+          +----------+
+ *   |              | (stdin)  |            |  (stdin) |          | 
+ *   |              |--------->|            |--------->|          |
+ *   | molvisviewer |          | pymolproxy |          | pymol    |
+ *   |  (client)    |          |            |          | (server) |(stderr)
+ *   |              | (stdout) |            | (stdout) |          |-------> file
+ *   |              |<---------|            |<---------|          | 
+ *   +--------------+          +------------+          +----------+
+ * 
+ * We're using a simple 2 thread setup: one for read client requests and
+ * relaying them to the pymol server, another reading pymol server output,
+ * and sending images by to the client.  The reason is because the client
+ * blocks on writes.  While it is sending requests or data, the proxy
+ * must be available to accept it.  Likewise, the proxy buffers images in a
+ * linked list when the client isn't ready to receive them.
+ *
+ * Reader thread:
+ * The communication between the pymol server and proxy is asynchronous.  
+ * The proxy translates commands from the client and sends them to the
+ * server without waiting for a response.  It watches the server's
+ * stdout in a separate thread snooping for image data.  
+ *
+ * Writer thread:
+ * The communication between the client and the proxy is also asynchronous.  
+ * The client commands are read when they become available on the socket.  
+ * The proxy writes images to the client when it can, otherwise it stores
+ * them in a list in memory.  This should prevent deadlocks from occuring:
+ * the client sends a request, while the proxy is writing an image.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/times.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <tcl.h>
+#include <pthread.h>
+#include <md5.h>
+
+#undef INLINE
+#ifdef __GNUC__
+#  define INLINE __inline__
+#else
+#  define INLINE
+#endif
+
+#define FALSE 0
+#define TRUE  1
+
+static int debug = FALSE;
+static FILE *frecord;
+static int recording = FALSE;
+static int pymolIsAlive = TRUE;
+static int statsFile = -1;
+
+#define WANT_DEBUG	0
+#define READ_DEBUG	0
+#define WRITE_DEBUG	0
+#define EXEC_DEBUG	0
+
+#define FORCE_UPDATE		(1<<0)
+#define CAN_UPDATE		(1<<1)
+#define INVALIDATE_CACHE	(1<<3)
+#define ATOM_SCALE_PENDING	(1<<4)
+#define STICK_RADIUS_PENDING	(1<<5)
+#define ROTATE_PENDING		(1<<6)
+#define PAN_PENDING		(1<<7)
+#define ZOOM_PENDING		(1<<8)
+#define UPDATE_PENDING		(1<<9)
+#define VIEWPORT_PENDING	(1<<10)
+
+#define IO_TIMEOUT (30000)
+#define CLIENT_READ		STDIN_FILENO
+#define CLIENT_WRITE		STDOUT_FILENO
+
+#ifndef LOGDIR
+#define LOGDIR		"/tmp"
+#endif	/* LOGDIR */
+
+#define CVT2SECS(x)  ((double)(x).tv_sec) + ((double)(x).tv_usec * 1.0e-6)
+
+typedef struct {
+    pid_t pid;				/* Child process. */
+    size_t numFrames;			/* # of frames sent to client. */
+    size_t numBytes;			/* # of bytes for all frames. */
+    size_t numCommands;			/* # of commands executed */
+    double cmdTime;			/* Elapsed time spend executing
+					 * commands. */
+    struct timeval start;		/* Start of elapsed time. */
+} Stats;
+
+static Stats stats;
+
+typedef struct Image {
+    struct Image *nextPtr;		/* Next image in chain of images. The
+					 * list is ordered by the most
+					 * recently received image from the
+					 * pymol server to the least. */
+    struct Image *prevPtr;		/* Previous image in chain of
+					 * images. The list is ordered by the
+					 * most recently received image from
+					 * the pymol server to the least. */
+    int id;
+    ssize_t numWritten;			/* Number of bytes of image data
+					 * already delivered.*/
+    size_t bytesLeft;			/* Number of bytes of image data left
+					 * to delivered to the client. */
+    unsigned char data[1];		/* Start of image data. We allocate
+					 * the size of the Image structure
+					 * plus the size of the image data. */
+} Image;
+
+#define BUFFER_SIZE		4096
+
+typedef struct {
+    Image *headPtr, *tailPtr;		/* List of images to be delivered to
+					 * the client.  The most recent images
+					 * are in the front of the list. */
+} ImageList;
+
+typedef struct {
+    const char *ident;
+    unsigned char *bytes;
+    size_t fill;
+    size_t mark;
+    size_t newline;
+    size_t bufferSize;
+    int lastStatus;
+    int fd;
+} ReadBuffer;
+
+#define BUFFER_OK		 0
+#define BUFFER_ERROR		-1
+#define BUFFER_CONTINUE		-2
+#define BUFFER_EOF		-3
+
+typedef struct {
+    Tcl_Interp *interp;
+    unsigned int flags;			/* Various flags. */
+    int sin, sout;			/* Server file descriptors. */
+    int cin, cout;			/* Client file descriptors. */
+    ReadBuffer client;			/* Read buffer for client input. */
+    ReadBuffer server;			/* Read buffer for server output. */
+    int frame;
+    int rockOffset;
+    int cacheId;
+    int error;
+    int status;
+    int width, height;			/* Size of viewport. */
+    float xAngle, yAngle, zAngle;	/* Euler angles of pending
+					 * rotation.  */
+    float sphereScale;			/* Atom scale of pending re-scale. */
+    float stickRadius;			/* Bond thickness of pending
+					 * re-scale. */
+    float zoom;
+    float xPan, yPan;
+    pid_t pid;
+} PymolProxy;
+
+#if WANT_DEBUG
+#define DEBUG(...)	if (debug) PrintToLog(__VA_ARGS__)
+#else 
+#define DEBUG(...) 
+#endif
+
+#define ERROR(...)      SysLog(LOG_ERR, __FILE__, __LINE__, __VA_ARGS__)
+#define TRACE(...)      SysLog(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
+#define WARN(...)       SysLog(LOG_WARNING, __FILE__, __LINE__, __VA_ARGS__)
+#define INFO(...)       SysLog(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
+
+static const char *syslogLevels[] = {
+    "emergency",			/* System is unusable */
+    "alert",				/* Action must be taken immediately */
+    "critical",				/* Critical conditions */
+    "error",				/* Error conditions */
+    "warning",				/* Warning conditions */
+    "notice",				/* Normal but significant condition */
+    "info",				/* Informational */
+    "debug",				/* Debug-level messages */
+};
+
+#if WANT_DEBUG
+static void
+PrintToLog TCL_VARARGS_DEF(const char *, arg1)
+{
+    const char *format;
+    va_list args;
+    
+    format = TCL_VARARGS_START(const char *, arg1, args);
+    fprintf(stderr, "pymolproxy: ");
+    vfprintf(stderr, format, args);
+    fprintf(stderr, "\n");
+    fflush(stderr);
+}
+#endif
+
+void
+SysLog(int priority, const char *path, int lineNum, const char* fmt, ...)
+{
+#define MSG_LEN (2047)
+    char message[MSG_LEN+1];
+    const char *s;
+    int length;
+    va_list lst;
+
+    va_start(lst, fmt);
+    s = strrchr(path, '/');
+    if (s == NULL) {
+        s = path;
+    } else {
+        s++;
+    }
+    length = snprintf(message, MSG_LEN, "pymolproxy2 (%d) %s: %s:%d ", 
+	getpid(), syslogLevels[priority],  s, lineNum);
+    length += vsnprintf(message + length, MSG_LEN - length, fmt, lst);
+    message[MSG_LEN] = '\0';
+    if (debug) {
+	DEBUG("%s\n", message);
+    } else {
+	syslog(priority, message, length);
+    }
+}
+
+static void
+Record TCL_VARARGS_DEF(const char *, arg1)
+{
+    const char *format;
+    va_list args;
+    
+    format = TCL_VARARGS_START(const char *, arg1, args);
+    vfprintf(frecord, format, args);
+    fflush(frecord);
+}
+
+static int
+SendToPymol(PymolProxy *proxyPtr, const char *format, ...)
+{
+    va_list ap;
+    char buffer[BUFSIZ];
+    int result;
+    ssize_t numWritten;
+    size_t length;
+
+    if (proxyPtr->error) {
+        return TCL_ERROR;
+    }
+    
+    va_start(ap, format);
+    result = vsnprintf(buffer, BUFSIZ-1, format, ap);
+    va_end(ap);
+    
+#ifdef EXEC_DEBUG
+    DEBUG("to-pymol>(%s) code=%d", buffer, result);
+#endif
+    if (recording) {
+	Record("%s\n", buffer);
+    }
+    
+    /* Write the command out to the server. */
+    length = strlen(buffer);
+    numWritten = write(proxyPtr->sin, buffer, length);
+    if (numWritten != length) {
+	ERROR("short write to pymol (wrote=%d, should have been %d): %s",
+	      numWritten, length, strerror(errno));
+    }
+    proxyPtr->status = result;
+    return  proxyPtr->status;
+}
+
+
+
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2011, Purdue Research Foundation
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+static void
+FlushReadBuffer(ReadBuffer *bp)
+{
+    bp->fill = bp->mark = 0;
+    bp->newline = 0;
+}
+
+/**
+ * \param[in] fd File descriptor to read
+ * \param[in] bufferSize Block size to use in internal buffer
+ */
+
+static void
+InitReadBuffer(ReadBuffer *bp, const char *id, int fd, size_t bufferSize)
+{
+    bp->ident = id;
+    bp->bufferSize = bufferSize;
+    bp->fd = fd;
+    bp->lastStatus = BUFFER_OK;
+    bp->bytes = malloc(bufferSize);
+    FlushReadBuffer(bp);
+}
+
+/**
+ * \brief Checks if a new line is currently in the buffer.
+ *
+ * \return the index of the character past the new line.
+ */
+static size_t
+NextLine(ReadBuffer *bp)
+{
+    /* Check for a newline in the current buffer. */
+
+    if (bp->newline > 0) {
+	return bp->newline;
+    } else {
+	unsigned char *p;
+	size_t newline;
+
+	p = (unsigned char *)memchr(bp->bytes + bp->mark, '\n', 
+				    bp->fill - bp->mark);
+	if (p == NULL) {
+	    newline = 0;
+	} else {
+	    newline = (p - bp->bytes + 1);
+	}
+	bp->newline = newline;
+	return newline;
+    }
+}
+
+/**  
+ * \brief Fills the buffer with available data.
+ * 
+ * Any existing data in the buffer is moved to the front of the buffer, 
+ * then the channel is read to fill the rest of the buffer.
+ *
+ * \return If an error occur when reading the channel, then ERROR is
+ * returned. ENDFILE is returned on EOF.  If the buffer can't be filled, 
+ * then CONTINUE is returned.
+ */
+static int 
+FillReadBuffer(ReadBuffer *bp)
+{
+    ssize_t numRead;
+    size_t bytesLeft;
+
+#if READ_DEBUG
+    DEBUG("Enter FillReadBuffer for %s: mark=%lu fill=%lu", 
+	  bp->ident, bp->mark, bp->fill);
+#endif
+    if (bp->mark >= bp->fill) {
+        FlushReadBuffer(bp);	/* Fully consumed buffer */
+    }
+    if (bp->mark > 0) {
+        /* Some data has been consumed. Move the unconsumed data to the front
+         * of the buffer. */
+#if READ_DEBUG
+        DEBUG("memmove %lu bytes", bp->fill - bp->mark);
+#endif
+        memmove(bp->bytes, bp->bytes + bp->mark, 
+		bp->fill - bp->mark);
+        bp->fill -= bp->mark;
+        bp->mark = 0;
+    }
+
+    bytesLeft = bp->bufferSize - bp->fill;
+#if READ_DEBUG
+    DEBUG("going to read %lu bytes", bytesLeft);
+#endif
+    numRead = read(bp->fd, bp->bytes + bp->fill, bytesLeft);
+    if (numRead == 0) {
+        /* EOF */
+#if READ_DEBUG
+        DEBUG("EOF found reading %s buffer (fill=%d): ", 
+	      bp->ident, bp->fill);
+#endif
+        return BUFFER_EOF;
+    }
+    if (numRead < 0) {
+        if (errno != EAGAIN) {
+            ERROR("error reading %s buffer: %s", bp->ident, strerror(errno));
+            return BUFFER_ERROR;
+        }
+#if READ_DEBUG
+        DEBUG("Short read for buffer");
+#endif
+        return BUFFER_CONTINUE;
+    }
+    bp->fill += numRead;
+#if READ_DEBUG
+    DEBUG("Read %lu bytes", numRead);
+#endif
+    return ((size_t)numRead == bytesLeft) ? BUFFER_OK : BUFFER_CONTINUE;
+}
+
+/**
+ * \brief Read the requested number of bytes from the buffer.
+
+ * Fails if the requested number of bytes are not immediately 
+ * available. Never should be short. 
+ */
+static int
+ReadFollowingData(ReadBuffer *bp, unsigned char *out, size_t numBytes)
+{
+#if READ_DEBUG
+    DEBUG("Enter ReadFollowingData %s", bp->ident);
+#endif
+    while (numBytes > 0) {
+        size_t bytesLeft;
+
+        bytesLeft = bp->fill - bp->mark;
+        if (bytesLeft > 0) {
+            int size;
+
+            /* Pull bytes out of the buffer, updating the mark. */
+            size = (bytesLeft >  numBytes) ? numBytes : bytesLeft;
+            memcpy(out, bp->bytes + bp->mark, size);
+            bp->mark += size;
+            numBytes -= size;
+            out += size;
+        }
+        if (numBytes == 0) {
+            /* Received requested # bytes. */
+            return BUFFER_OK;
+        }
+        /* Didn't get enough bytes, need to read some more. */
+        bp->lastStatus = FillReadBuffer(bp);
+        if ((bp->lastStatus == BUFFER_ERROR) || 
+	    (bp->lastStatus == BUFFER_EOF)) {
+            return bp->lastStatus;
+        }
+    }
+    return BUFFER_OK;
+}
+
+/** 
+ * \brief Returns the next available line (terminated by a newline)
+ * 
+ * If insufficient data is in the buffer, then the channel is
+ * read for more data.  If reading the channel results in a
+ * short read, CONTINUE is returned and *numBytesPtr is set to 0.
+ */
+static int
+GetLine(ReadBuffer *bp, size_t *numBytesPtr, const char **bytesPtr)
+{
+#if READ_DEBUG
+    DEBUG("Enter GetLine");
+#endif
+    *numBytesPtr = 0;
+    *bytesPtr = NULL;
+
+    bp->lastStatus = BUFFER_OK;
+    for (;;) {
+        size_t newline;
+
+        newline = NextLine(bp);
+        if (newline > 0) {
+            /* Start of the line. */
+            *bytesPtr = (const char *)(bp->bytes + bp->mark);
+            /* Number of bytes in the line. */
+            *numBytesPtr = newline - bp->mark;
+            bp->mark = newline;
+	    bp->newline = 0;
+            return BUFFER_OK;
+        }
+        /* Couldn't find a newline, so it may be that we need to read some
+         * more. Check first that last read wasn't a short read. */
+        if (bp->lastStatus == BUFFER_CONTINUE) {
+            /* No complete line just yet. */
+            return BUFFER_CONTINUE;
+        }
+        /* Try to add more data to the buffer. */
+        bp->lastStatus = FillReadBuffer(bp);
+        if (bp->lastStatus == BUFFER_ERROR ||
+            bp->lastStatus == BUFFER_EOF) {
+            return bp->lastStatus;
+        }
+        /* OK or CONTINUE */
+    }
+    return BUFFER_CONTINUE;
+}
+
+static int
+IsLineAvailable(ReadBuffer *bp)
+{
+    return (NextLine(bp) > 0);
+}
+
+static int
+WaitForNextLine(ReadBuffer *bp, struct timeval *tvPtr)
+{
+    fd_set readFds;
+    int n;
+
+    if (IsLineAvailable(bp)) {
+	return 1;
+    }
+    FD_ZERO(&readFds);
+    FD_SET(bp->fd, &readFds);
+    n = select(bp->fd+1, &readFds, NULL, NULL, tvPtr);
+    return (n > 0);
+}
+
+INLINE static void
+clear_error(PymolProxy *proxyPtr)
+{
+    proxyPtr->error = 0;
+    proxyPtr->status = TCL_OK;
+}
+
+#ifndef STATSDIR
+#define STATSDIR	"/var/tmp/visservers"
+#endif  /*STATSDIR*/
+
+static int
+GetStatsFile(const char *string)
+{
+    Tcl_DString ds;
+    int i;
+    char fileName[33];
+    const char *path;
+    int length;
+    char pidstr[200];
+    md5_state_t state;
+    md5_byte_t digest[16];
+
+    if ((string == NULL) || (statsFile >= 0)) {
+	return statsFile;
+    }
+    /* By itself the client's key/value pairs aren't unique.  Add in the
+     * process id of this render server. */
+    Tcl_DStringInit(&ds);
+    Tcl_DStringAppend(&ds, string, -1);
+    Tcl_DStringAppendElement(&ds, "pid");
+    sprintf(pidstr, "%d", getpid());
+    Tcl_DStringAppendElement(&ds, pidstr);
+
+    /* Create a md5 hash of the key/value pairs and use it as the file name. */
+    string = Tcl_DStringValue(&ds);
+    length = strlen(string);
+    md5_init(&state);
+    md5_append(&state, (const md5_byte_t *)string, length);
+    md5_finish(&state, digest);
+    for (i = 0; i < 16; i++) {
+        sprintf(fileName + i * 2, "%02x", digest[i]);
+    }
+    Tcl_DStringSetLength(&ds, 0);
+    Tcl_DStringAppend(&ds, STATSDIR, -1);
+    Tcl_DStringAppend(&ds, "/", 1);
+    Tcl_DStringAppend(&ds, fileName, 32);
+    path = Tcl_DStringValue(&ds);
+
+    statsFile = open(path, O_EXCL | O_CREAT | O_WRONLY, 0600);
+    Tcl_DStringFree(&ds);
+    if (statsFile < 0) {
+	ERROR("can't open \"%s\": %s", fileName, strerror(errno));
+	return -1;
+    }
+    return statsFile;
+}
+
+static int
+WriteToStatsFile(int f, const char *s, size_t length)
+{
+
+    if (f >= 0) {
+	ssize_t numWritten;
+
+        numWritten = write(f, s, length);
+        if (numWritten == (ssize_t)length) {
+            close(dup(f));
+        }
+    }
+    return 0;
+}
+
+static int
+ServerStats(int code) 
+{
+    double start, finish;
+    char buf[BUFSIZ];
+    Tcl_DString ds;
+    int result;
+    int f;
+
+    {
+	struct timeval tv;
+
+	/* Get ending time.  */
+	gettimeofday(&tv, NULL);
+	finish = CVT2SECS(tv);
+	tv = stats.start;
+	start = CVT2SECS(tv);
+    }
+    /* 
+     * Session information:
+     *   - Name of render server
+     *   - Process ID
+     *   - Hostname where server is running
+     *   - Start date of session
+     *   - Start date of session in seconds
+     *   - Number of frames returned
+     *   - Number of bytes total returned (in frames)
+     *   - Number of commands received
+     *   - Total elapsed time of all commands
+     *   - Total elapsed time of session
+     *   - Exit code of vizserver
+     *   - User time 
+     *   - System time
+     *   - User time of children 
+     *   - System time of children 
+     */ 
+
+    Tcl_DStringInit(&ds);
+    
+    Tcl_DStringAppendElement(&ds, "render_stop");
+    /* renderer */
+    Tcl_DStringAppendElement(&ds, "renderer");
+    Tcl_DStringAppendElement(&ds, "pymol");
+    /* pid */
+    Tcl_DStringAppendElement(&ds, "pid");
+    sprintf(buf, "%d", getpid());
+    Tcl_DStringAppendElement(&ds, buf);
+    /* host */
+    Tcl_DStringAppendElement(&ds, "host");
+    gethostname(buf, BUFSIZ-1);
+    buf[BUFSIZ-1] = '\0';
+    Tcl_DStringAppendElement(&ds, buf);
+    /* date */
+    Tcl_DStringAppendElement(&ds, "date");
+    strcpy(buf, ctime(&stats.start.tv_sec));
+    buf[strlen(buf) - 1] = '\0';
+    Tcl_DStringAppendElement(&ds, buf);
+    /* date_secs */
+    Tcl_DStringAppendElement(&ds, "date_secs");
+    sprintf(buf, "%ld", stats.start.tv_sec);
+    Tcl_DStringAppendElement(&ds, buf);
+    /* num_frames */
+    Tcl_DStringAppendElement(&ds, "num_frames");
+    sprintf(buf, "%lu", (unsigned long int)stats.numFrames);
+    Tcl_DStringAppendElement(&ds, buf);
+    /* frame_bytes */
+    Tcl_DStringAppendElement(&ds, "frame_bytes");
+    sprintf(buf, "%lu", (unsigned long int)stats.numBytes);
+    Tcl_DStringAppendElement(&ds, buf);
+    /* num_commands */
+    Tcl_DStringAppendElement(&ds, "num_commands");
+    sprintf(buf, "%lu", (unsigned long int)stats.numCommands);
+    Tcl_DStringAppendElement(&ds, buf);
+    /* cmd_time */
+    Tcl_DStringAppendElement(&ds, "cmd_time");
+    sprintf(buf, "%g", stats.cmdTime);
+    Tcl_DStringAppendElement(&ds, buf);
+    /* session_time */
+    Tcl_DStringAppendElement(&ds, "session_time");
+    sprintf(buf, "%g", finish - start);
+    Tcl_DStringAppendElement(&ds, buf);
+    /* status */
+    Tcl_DStringAppendElement(&ds, "status");
+    sprintf(buf, "%d", code);
+    Tcl_DStringAppendElement(&ds, buf);
+    {
+	long clocksPerSec = sysconf(_SC_CLK_TCK);
+	double clockRes = 1.0 / clocksPerSec;
+	struct tms tms;
+
+	memset(&tms, 0, sizeof(tms));
+	times(&tms);
+	/* utime */
+	Tcl_DStringAppendElement(&ds, "utime");
+	sprintf(buf, "%g", tms.tms_utime * clockRes);
+	Tcl_DStringAppendElement(&ds, buf);
+	/* stime */
+	Tcl_DStringAppendElement(&ds, "stime");
+	sprintf(buf, "%g", tms.tms_stime * clockRes);
+	Tcl_DStringAppendElement(&ds, buf);
+	/* cutime */
+	Tcl_DStringAppendElement(&ds, "cutime");
+	sprintf(buf, "%g", tms.tms_cutime * clockRes);
+	Tcl_DStringAppendElement(&ds, buf);
+	/* cstime */
+	Tcl_DStringAppendElement(&ds, "cstime");
+	sprintf(buf, "%g", tms.tms_cstime * clockRes);
+	Tcl_DStringAppendElement(&ds, buf);
+    }
+    Tcl_DStringAppend(&ds, "\n", -1);
+    f = GetStatsFile(NULL);
+    result = WriteToStatsFile(f, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds));
+    Tcl_DStringFree(&ds);
+    close(f);
+    return result;
+}
+
+
+static int
+CartoonCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	   const char *argv[])
+{
+    PymolProxy *p = clientData;
+    int bool, defer, push, i;
+    const char *model;
+
+    clear_error(p);
+    defer = push = FALSE;
+    model = "all";
+    bool = FALSE;
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0) {
+            defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = TRUE;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc) {
+                model = argv[i];
+	    }
+	} else { 
+	    if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	}
+    }
+    p->flags |= INVALIDATE_CACHE;	/* cartoon */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    if (bool) {
+	SendToPymol(p, "show cartoon,%s\n", model);
+    } else {
+	SendToPymol(p, "hide cartoon,%s\n", model);
+    }
+    return p->status;
+}
+
+static int
+CartoonTraceCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+		const char *argv[])
+{
+    PymolProxy *p = clientData;
+    int bool, defer, push, i;
+    const char *model;
+
+    clear_error(p);
+    defer = push = bool = FALSE;
+    model = "all";
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0) {
+            defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = TRUE;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc) {
+                model = argv[i];
+	    }
+	} else { 
+	    if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	}
+    }
+    p->flags |= INVALIDATE_CACHE; /* cartoon_trace  */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    SendToPymol(p, "set cartoon_trace,%d,%s\n", bool, model);
+    return p->status;
+}
+
+static int
+DisableCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	   const char *argv[])
+{
+    PymolProxy *p = clientData;
+    const char *model = "all";
+    int i, defer, push;
+
+    clear_error(p);
+    defer = push = FALSE;
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i], "-defer") == 0 )
+            defer = 1;
+        else if (strcmp(argv[i], "-push") == 0 )
+            push = 1;
+        else
+            model = argv[i];
+    }
+
+    p->flags |= INVALIDATE_CACHE;	/* disable */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    SendToPymol(p, "disable %s\n", model);
+    return p->status;
+}
+
+
+static int
+EnableCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	  const char *argv[])
+{
+    PymolProxy *p = clientData;
+    const char *model;
+    int i, defer, push;
+
+    clear_error(p);
+    push = defer = FALSE;
+    model = "all";
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0) {
+            defer = TRUE;
+	} else if (strcmp(argv[i], "-push") == 0) {
+            push = TRUE;
+	} else {
+            model = argv[i];
+	}
+    }
+    p->flags |= INVALIDATE_CACHE; /* enable */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    SendToPymol(p, "enable %s\n", model);
+    return p->status;
+}
+
+static int
+FrameCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	 const char *argv[])
+{
+    PymolProxy *p = clientData;
+    int i, push, defer, frame;
+
+    clear_error(p);
+    frame = 0;
+    push = defer = FALSE;
+    for(i = 1; i < argc; i++) { 
+        if (strcmp(argv[i],"-defer") == 0) {
+            defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = TRUE;
+	} else {
+            frame = atoi(argv[i]);
+	}
+    }
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    p->frame = frame;
+
+    /* Does not invalidate cache? */
+
+    SendToPymol(p,"frame %d\n", frame);
+    return p->status;
+}
+
+/* 
+ * ClientInfoCmd --
+ *
+ *	  
+ *	clientinfo path list
+ */
+static int
+ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	      const char *argv[])
+{
+    Tcl_DString ds;
+    int result;
+    int i, numElems;
+    const char **elems;
+    char buf[BUFSIZ];
+    static int first = 1;
+    int f;
+
+    if (argc != 2) {
+	Tcl_AppendResult(interp, "wrong # of arguments: should be \"", argv[0],
+		" list\"", (char *)NULL);
+	return TCL_ERROR;
+    }
+    /* Use the initial client key value pairs as the parts for a generating
+     * a unique file name. */
+    f = GetStatsFile(argv[1]);
+    if (f < 0) {
+	Tcl_AppendResult(interp, "can't open stats file: ", 
+                         Tcl_PosixError(interp), (char *)NULL);
+	return TCL_ERROR;
+    }
+    Tcl_DStringInit(&ds);
+    if (first) {
+        first = 0;
+        Tcl_DStringAppendElement(&ds, "render_start");
+        /* renderer */
+        Tcl_DStringAppendElement(&ds, "renderer");
+	Tcl_DStringAppendElement(&ds, "pymol");
+        /* pid */
+	Tcl_DStringAppendElement(&ds, "pid");
+	sprintf(buf, "%d", getpid());
+	Tcl_DStringAppendElement(&ds, buf);
+        /* host */
+        Tcl_DStringAppendElement(&ds, "host");
+	gethostname(buf, BUFSIZ-1);
+	buf[BUFSIZ-1] = '\0';
+	Tcl_DStringAppendElement(&ds, buf);
+    } else {
+        Tcl_DStringAppendElement(&ds, "render_info");
+    }
+    /* date */
+    Tcl_DStringAppendElement(&ds, "date");
+    strcpy(buf, ctime(&stats.start.tv_sec));
+    buf[strlen(buf) - 1] = '\0';
+    Tcl_DStringAppendElement(&ds, buf);
+    /* date_secs */
+    Tcl_DStringAppendElement(&ds, "date_secs");
+    sprintf(buf, "%ld", stats.start.tv_sec);
+    Tcl_DStringAppendElement(&ds, buf);
+
+    /* Client arguments. */
+    if (Tcl_SplitList(interp, argv[1], &numElems, &elems) != TCL_OK) {
+	return TCL_ERROR;
+    }
+    for (i = 0; i < numElems; i++) {
+	Tcl_DStringAppendElement(&ds, elems[i]);
+    }
+    free(elems);
+    Tcl_DStringAppend(&ds, "\n", 1);
+    result = WriteToStatsFile(f, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds));
+    Tcl_DStringFree(&ds);
+    return result;
+}
+
+static int
+LabelCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	 const char *argv[])
+{
+    PymolProxy *p = clientData;
+    int i, push, defer, bool, size;
+    const char *model;
+
+    clear_error(p);
+    model = "all";
+    size = 14;
+    bool = TRUE;
+    push = defer = FALSE;
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0) {
+            defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = TRUE;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc) {
+                model = argv[i];
+	    }
+	} else if (strcmp(argv[i],"-size") == 0) {
+            if (++i < argc) {
+                size = atoi(argv[i]);
+	    }
+	} else if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
+	    return TCL_ERROR;
+	}
+    }
+    p->flags |= INVALIDATE_CACHE;	/* label */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    SendToPymol(p, "set label_color,white,%s\nset label_size,%d,%s\n", 
+	    model, size, model);
+    if (bool) {
+        SendToPymol(p, "label %s,\"%%s%%s\" %% (ID,name)\n", model);
+    } else {
+        SendToPymol(p, "label %s\n", model);
+    }
+    return p->status;
+}
+
+/* 
+ * LoadPDBCmd --
+ *
+ *	Load a PDB into pymol.  We write the pdb data into a temporary file
+ *	and then let pymol read it and delete it.  There is no good way to
+ *	load PDB data into pymol without using a file.  The specially created
+ *	routine "loadandremovepdbfile" in pymol will remove the file after
+ *	loading it.  
+ *
+ */
+static int
+LoadPDBCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	   const char *argv[])
+{
+    PymolProxy *p = clientData;
+    const char *string;
+    const char *name;
+    unsigned char *allocated;
+    int state, defer, push;
+    size_t numBytes;
+    int i, j;
+    int status;
+
+    if (p == NULL){
+	return TCL_ERROR;
+    }
+    clear_error(p);
+    defer = push = FALSE;
+    for(i = j = 1; i < argc; i++) {
+	if (strcmp(argv[i],"-defer") == 0) {
+	    defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+	    push = TRUE;
+	} else {
+	    if (j < i) {
+		argv[j] = argv[i];
+	    }
+	    j++;
+	}
+    }
+    argc = j;
+    if (argc < 4) {
+	Tcl_AppendResult(interp, "wrong # arguments: should be \"", argv[0],
+			 " <data>|follows <model> <state> ?<numBytes>?\"",
+			 (char *)NULL);
+	return TCL_ERROR;
+    }
+    string = argv[1];
+    name   = argv[2];
+    if (Tcl_GetInt(interp, argv[3], &state) != TCL_OK) {
+	return TCL_ERROR;
+    }
+    numBytes = 0;
+    status = BUFFER_ERROR;
+    if (strcmp(string, "follows") == 0) {
+	int n;
+
+	if (argc != 5) {
+	    Tcl_AppendResult(interp, "wrong # arguments: should be \"", argv[0],
+			 " follows <model> <state> <numBytes>\"", (char *)NULL);
+	    return TCL_ERROR;
+	}
+	if (Tcl_GetInt(interp, argv[4], &n) != TCL_OK) {
+	    return TCL_ERROR;
+	}
+	if (n < 0) {
+	    Tcl_AppendResult(interp, "bad value for # bytes \"", argv[4],
+			 "\"", (char *)NULL);
+	    return TCL_ERROR;
+	}
+	numBytes = (size_t)n;
+    }
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    p->cacheId = state;
+
+    /* Does not invalidate cache? */
+
+    allocated = NULL;
+    allocated = malloc(sizeof(char) * numBytes);
+    if (allocated == NULL) {
+	Tcl_AppendResult(interp, "can't allocate buffer for pdbdata.",
+			 (char *)NULL);
+	return TCL_ERROR;
+    }
+    status = ReadFollowingData(&p->client, allocated, numBytes);
+    if (status != BUFFER_OK) {
+	Tcl_AppendResult(interp, "can't read pdbdata from client.",
+			 (char *)NULL);
+	free(allocated);
+	return TCL_ERROR;
+    }
+    string = (const char *)allocated;
+    {
+	int f;
+	ssize_t numWritten;
+	char fileName[200];
+
+	strcpy(fileName, "/tmp/pdb.XXXXXX");
+	p->status = TCL_ERROR;
+	f = mkstemp(fileName);
+	if (f < 0) {
+	    Tcl_AppendResult(interp, "can't create temporary file \"", 
+		fileName, "\":", Tcl_PosixError(interp), (char *)NULL);
+	    goto error;
+	} 
+	numWritten = write(f, string, numBytes);
+	if (numBytes != numWritten) {
+	    Tcl_AppendResult(interp, "can't write PDB data to \"",
+		fileName, "\": ", Tcl_PosixError(interp), (char *)NULL);
+	    close(f);
+	    goto error;
+	}
+	close(f);
+	SendToPymol(p, "loadandremovepdbfile %s,%s,%d\n", fileName, name, 
+		    state);
+	p->status = TCL_OK;
+    }
+ error:
+    if (allocated != NULL) {
+	free(allocated);
+    }
+    return p->status;
+}
+
+static int
+OrthoscopicCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	      const char *argv[])
+{
+    PymolProxy *p = clientData;
+    int bool, defer, push, i;
+
+    clear_error(p);
+    defer = push = FALSE;
+    bool = FALSE;
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0) {
+            defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = TRUE;
+	} else { 
+	    if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	}
+    }
+    p->flags |= INVALIDATE_CACHE; /* orthoscopic */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    SendToPymol(p, "set orthoscopic=%d\n", bool);
+    return p->status;
+}
+
+/* 
+ * PanCmd --
+ *
+ *	Issue "move" commands for changes in the x and y coordinates of the
+ *	camera.  The problem here is that there is no event compression.
+ *	Consecutive "pan" commands are not compressed into a single
+ *	directive.  The means that the pymol server will render scenes that
+ *	are not used by the client.
+ *
+ *	Need to 1) defer the "move" commands until we find the next command
+ *	isn't a "pan". 2) Track the x and y coordinates as they are
+ *	compressed.
+ */
+static int
+PanCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
+{
+    PymolProxy *p = clientData;
+    double x, y;
+    int i;
+    int defer, push;
+
+    clear_error(p);
+    defer = push = FALSE;
+    for (i = 1; i < argc; i++) {
+	if (strcmp(argv[i],"-defer") == 0) {
+	    defer = 1;
+	} else if (strcmp(argv[i],"-push") == 0) {
+	    push = 1;
+	} else { 
+	    break;
+	}
+    }
+    if ((Tcl_GetDouble(interp, argv[i], &x) != TCL_OK) ||
+	(Tcl_GetDouble(interp, argv[i+1], &y) != TCL_OK)) {
+	return TCL_ERROR;
+    }
+    p->flags |= INVALIDATE_CACHE;	/* pan */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    if ((x != 0.0f) || (y != 0.0f)) {
+	p->xPan = x * 0.05;
+	p->yPan = -y * 0.05;
+	p->flags |= PAN_PENDING;
+    }
+    return p->status;
+}
+
+static int
+PngCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
+{
+    PymolProxy *p = clientData;
+
+    clear_error(p);
+
+    /* Force pymol to update the current scene. */
+    SendToPymol(p, "refresh\n");
+    /* This is a hack. We're encoding the filename to pass extra information
+     * to the MyPNGWrite routine inside of pymol. Ideally these would be
+     * parameters of a new "molvispng" command that would be passed all the
+     * way through to MyPNGWrite.
+     *
+     * The extra information is contained in the token we get from the
+     * molvisviewer client, the frame number, and rock offset. */
+    SendToPymol(p, "png -:%d:%d:%d\n", p->cacheId, p->frame, p->rockOffset);
+    return p->status;
+}
+
+
+static int
+PpmCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
+{
+    PymolProxy *p = clientData;
+
+    clear_error(p);
+
+    /* Force pymol to update the current scene. */
+    SendToPymol(p, "refresh\n");
+    /* This is a hack. We're encoding the filename to pass extra information
+     * to the MyPNGWrite routine inside of pymol. Ideally these would be
+     * parameters of a new "molvispng" command that would be passed all the
+     * way through to MyPNGWrite.
+     *
+     * The extra information is contained in the token we get from the
+     * molvisviewer client, the frame number, and rock offset. */
+    SendToPymol(p, "png -:%d:%d:%d,format=1\n", p->cacheId, p->frame, 
+	    p->rockOffset);
+    p->flags &= ~(UPDATE_PENDING|FORCE_UPDATE);
+    return p->status;
+}
+
+
+static int
+PrintCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	 const char *argv[])
+{
+    PymolProxy *p = clientData;
+    int width, height;
+    const char *token, *bgcolor;
+
+    clear_error(p);
+
+    if (argc != 5) {
+	Tcl_AppendResult(interp, "wrong # arguments: should be \"", 
+			 argv[0], " token width height color\"", (char *)NULL);
+	return TCL_ERROR;
+    }
+    token = argv[1];
+    if (Tcl_GetInt(interp, argv[2], &width) != TCL_OK) {
+	return TCL_ERROR;
+    }
+    if (Tcl_GetInt(interp, argv[3], &height) != TCL_OK) {
+	return TCL_ERROR;
+    }
+    bgcolor = argv[4];
+    /* Force pymol to update the current scene. */
+    if (strcmp(bgcolor, "none") == 0) {
+	SendToPymol(p, "set ray_opaque_background,off\n");
+	SendToPymol(p, "refresh\n", bgcolor);
+    } else {
+	SendToPymol(p, "set ray_opaque_background,on\n");
+	SendToPymol(p, "bg_color %s\nrefresh\n", bgcolor);
+    }
+    /* This is a hack. We're encoding the filename to pass extra information
+     * to the MyPNGWrite routine inside of pymol. Ideally these would be
+     * parameters of a new "molvispng" command that would be passed all the
+     * way through to MyPNGWrite.  
+     *
+     * The extra information is contained in the token we get from the
+     * molvisviewer client, the frame number, and rock offset.
+     */
+    SendToPymol(p, "png -:%s:0:0,width=%d,height=%d,ray=1,dpi=300\n", 
+	    token, width, height);
+    SendToPymol(p, "bg_color black\n");
+    return p->status;
+}
+
+static int
+RawCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
+{
+    PymolProxy *p = clientData;
+    int arg, defer = 0, push = 0;
+    const char *cmd;
+    clear_error(p);
+
+    cmd = NULL;
+    defer = push = FALSE;
+    for(arg = 1; arg < argc; arg++) {
+        if (strcmp(argv[arg], "-defer") == 0)
+            defer = 1;
+        else if (strcmp(argv[arg], "-push") == 0)
+            push = 1;
+        else {
+            cmd = argv[arg];
+        }
+    }
+
+    p->flags |= INVALIDATE_CACHE; /* raw */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    SendToPymol(p,"%s\n", cmd);
+    return p->status;
+}
+
+static int
+ResetCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	 const char *argv[])
+{
+    PymolProxy *p = clientData;
+    int arg, push = 0, defer = 0;
+
+    clear_error(p);
+    defer = push = 0;
+    for(arg = 1; arg < argc; arg++) {
+        if ( strcmp(argv[arg],"-defer") == 0 )
+            defer = 1;
+        else if (strcmp(argv[arg],"-push") == 0 )
+            push = 1;
+    }
+                
+    p->flags |= INVALIDATE_CACHE; /* reset */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    SendToPymol(p, "reset\nzoom complete=1\n");
+    return p->status;
+}
+
+static int
+RockCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	const char *argv[])
+{
+    PymolProxy *p = clientData;
+    float y = 0.0;
+    int arg, push, defer;
+
+    clear_error(p);
+
+    defer = push = FALSE;
+    for(arg = 1; arg < argc; arg++) {
+        if ( strcmp(argv[arg],"-defer") == 0 )
+            defer = 1;
+        else if (strcmp(argv[arg],"-push") == 0 )
+            push = 1;
+        else
+            y = atof( argv[arg] );
+    }
+                
+    /* Does not invalidate cache. */
+
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    SendToPymol(p,"turn y, %f\n", y - p->rockOffset);
+    p->rockOffset = y;
+    return p->status;
+}
+
+static int
+RepresentationCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+		  const char *argv[])
+{
+    PymolProxy *p = clientData;
+    const char *model;
+    const char *rep;
+    int defer, push, i;
+
+    clear_error(p);
+    defer = push = FALSE;
+    model = "all";
+    rep = NULL;
+    for (i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0 ) {
+            defer = TRUE;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = TRUE;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc) {
+                model = argv[i];
+	    }
+        } else {
+            rep = argv[i];
+	}
+    }
+    if (rep == NULL) {
+	Tcl_AppendResult(interp, "missing representation argument",
+			 (char *)NULL);
+	return TCL_ERROR;
+    }
+
+    p->flags |= INVALIDATE_CACHE; /* representation */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    if (strcmp(rep, "ballnstick") == 0) { /* Ball 'n Stick */
+	SendToPymol(p, 
+	      "set stick_color,white,%s\n"
+	      "show sticks,%s\n"
+	      "show spheres,%s\n"
+	      "hide lines,%s\n"
+	      "hide cartoon,%s\n",
+	      model, model, model, model, model);
+    } else if (strcmp(rep, "spheres") == 0) { /* spheres */    
+	SendToPymol(p, 
+	      "hide sticks,%s\n"
+	      "show spheres,%s\n"
+	      "hide lines,%s\n"
+	      "hide cartoon,%s\n"
+	      "set sphere_quality,2,%s\n"
+	      "set ambient,.2,%s\n", 
+	      model, model, model, model, model, model);
+    } else if (strcmp(rep, "none") == 0) { /* nothing */    
+	SendToPymol(p, 
+	      "hide sticks,%s\n", 
+	      "hide spheres,%s\n"
+	      "hide lines,%s\n"
+	      "hide cartoon,%s\n",
+	      model, model, model, model);
+    } else if (strcmp(rep, "sticks") == 0) { /* sticks */    
+	SendToPymol(p, 
+	      "set stick_color,white,%s\n"
+	      "show sticks,%s\n"
+	      "hide spheres,%s\n"
+	      "hide lines,%s\n"
+	      "hide cartoon,%s\n",
+	      model, model, model, model, model);
+    } else if (strcmp(rep, "lines") == 0) { /* lines */    
+	SendToPymol(p, 
+	      "hide sticks,%s\n"
+	      "hide spheres,%s\n"
+	      "show lines,%s\n"
+	      "hide cartoon,%s\n",
+	      model, model, model, model);
+    } else if (strcmp(rep, "cartoon") == 0) { /* cartoon */    
+	SendToPymol(p, 
+	      "hide sticks,%s\n"
+	      "hide spheres,%s\n"
+	      "hide lines,%s\n"
+	      "show cartoon,%s\n", 
+	      model, model, model, model);
+    } 
+    return p->status;
+}
+
+/* 
+ * RotateCmd --
+ *
+ *	Issue "turn" commands for changes in the angle of the camera.  The
+ *	problem here is that there is no event compression.  Consecutive
+ *	"rotate" commands are not compressed into a single directive.  The
+ *	means that the pymol server will render many scene that are not used
+ *	by the client.
+ *
+ *	Need to 1) defer the "turn" commands until we find the next command
+ *	isn't a "rotate". 2) Track the rotation angles as they are compressed.
+ */
+static int
+RotateCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	  const char *argv[])
+{
+    PymolProxy *p = clientData;
+    float xAngle, yAngle, zAngle;
+    int defer, push, arg, varg = 1;
+
+    clear_error(p);
+    defer = push = 0;
+    xAngle = yAngle = zAngle = 0.0f;
+    for(arg = 1; arg < argc; arg++) {
+	if (strcmp(argv[arg],"-defer") == 0) {
+	    defer = 1; 
+	} else if (strcmp(argv[arg],"-push") == 0) {
+	    push = 1;
+	} else  if (varg == 1) {
+	    double value;
+	    if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	    xAngle = (float)value;
+	    varg++;
+	} else if (varg == 2) {
+	    double value;
+	    if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	    yAngle = (float)value;
+	    varg++;
+	} else if (varg == 3) {
+	    double value;
+	    if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	    zAngle = (float)value;
+	    varg++;
+	}
+    } 
+    p->flags |= INVALIDATE_CACHE; /* rotate */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    if ((xAngle != 0.0f) || (yAngle != 0.0f) || (zAngle != 0.0f)) {
+	p->xAngle += xAngle;
+	p->yAngle += yAngle;
+	p->zAngle += zAngle;
+	p->flags |= ROTATE_PENDING;
+    }
+    return p->status;
+}
+
+static int
+ScreenCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	  const char *argv[])
+{
+    PymolProxy *p = clientData;
+    int width = -1, height = -1;
+    int defer, push, i, varg;
+
+    clear_error(p);
+    defer = push = FALSE;
+    varg = 1;
+    for(i = 1; i < argc; i++) {
+        if ( strcmp(argv[i],"-defer") == 0 ) 
+            defer = 1;
+        else if ( strcmp(argv[i], "-push") == 0 )
+            push = 1;
+        else if (varg == 1) {
+            width = atoi(argv[i]);
+            height = width;
+            varg++;
+        }
+        else if (varg == 2) {
+            height = atoi(argv[i]);
+            varg++;
+        }
+    }
+    if ((width < 0) || (height < 0)) {
+	return TCL_ERROR;
+    }
+    p->flags |= INVALIDATE_CACHE; /* viewport */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    p->width = width;
+    p->height = height;
+    p->flags |= VIEWPORT_PENDING;
+    return p->status;
+}
+
+static int
+SphereScaleCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	   const char *argv[])
+{
+    int defer = 0, push = 0, i;
+    double scale;
+    const char *model = "all";
+    PymolProxy *p = clientData;
+
+    clear_error(p);
+    scale = 0.25f;
+    for(i = 1; i < argc; i++) {
+        if ( strcmp(argv[i],"-defer") == 0 ) {
+            defer = 1;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = 1;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc) {
+                model = argv[i];
+	    }
+        } else {
+	    if (Tcl_GetDouble(interp, argv[i], &scale) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	}
+    }
+    p->flags |= INVALIDATE_CACHE;  /* sphere_scale */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    if (strcmp(model, "all") == 0) {
+	p->flags |= ATOM_SCALE_PENDING;
+	p->sphereScale = scale;
+    } else {
+	SendToPymol(p, "set sphere_scale,%f,%s\n", scale, model);
+    }
+    return p->status;
+}
+
+static int
+StickRadiusCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	       const char *argv[])
+{
+    PymolProxy *p = clientData;
+    int defer = 0, push = 0, i;
+    double scale;
+    const char *model = "all";
+
+    clear_error(p);
+    scale = 0.25f;
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i],"-defer") == 0 ) {
+            defer = 1;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = 1;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc)
+                model = argv[i];
+        } else {
+	    if (Tcl_GetDouble(interp, argv[i], &scale) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	}
+    }
+    p->flags |= INVALIDATE_CACHE;  /* stick_radius */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+
+    if (strcmp(model, "all") == 0) {
+	p->flags |= STICK_RADIUS_PENDING;
+	p->stickRadius = scale;
+    } else {
+	SendToPymol(p, "set stick_radius,%f,%s\n", scale, model);
+    }
+    return p->status;
+}
+
+static int
+TransparencyCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+		const char *argv[])
+{
+    PymolProxy *p = clientData;
+    const char *model;
+    float transparency;
+    int defer, push;
+    int i;
+
+    clear_error(p);
+    model = "all";
+    defer = push = FALSE;
+    transparency = 0.0f;
+    for(i = 1; i < argc; i++) {
+        if ( strcmp(argv[i],"-defer") == 0 ) {
+            defer = 1;
+	} else if (strcmp(argv[i],"-push") == 0) {
+            push = 1;
+	} else if (strcmp(argv[i],"-model") == 0) {
+            if (++i < argc) {
+                model = argv[i];
+	    }
+	} else {
+	    transparency = atof(argv[i]);
+	}
+    }
+    p->flags |= INVALIDATE_CACHE; /* transparency */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    } 
+    SendToPymol(p, 
+	  "set sphere_transparency,%g,%s\n"
+	  "set stick_transparency,%g,%s\n"
+	  "set cartoon_transparency,%g,%s\n",
+	  transparency, model, transparency, model, 
+	  transparency, model);
+    return p->status;
+}
+
+static int
+VMouseCmd(ClientData clientData, Tcl_Interp *interp, int argc, 
+	  const char *argv[])
+{
+    PymolProxy *p = clientData;
+    int i, defer = 0, push = 0, varg = 1;
+    int arg1 = 0, arg2 = 0, arg3 = 0, arg4 = 0, arg5 = 0;
+
+    clear_error(p);
+
+    for(i = 1; i < argc; i++) {
+        if (strcmp(argv[i], "-defer") == 0)
+            defer = 1;
+        else if (strcmp(argv[i], "-push") == 0)
+            push = 1;
+        else if (varg == 1) {
+            arg1 = atoi(argv[i]);
+            varg++;
+        } else if (varg == 2) {
+            arg2 = atoi(argv[i]);
+            varg++;
+        } else if (varg == 3) {
+            arg3 = atoi(argv[i]);
+            varg++;
+        } else if (varg == 4) {
+            arg4 = atoi(argv[i]);
+            varg++;
+        } else if (varg == 5) {
+            arg5 = atoi(argv[i]);
+            varg++;
+        }
+    }
+
+    p->flags |= INVALIDATE_CACHE;	/* vmouse */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    SendToPymol(p, "vmouse %d,%d,%d,%d,%d\n", arg1, arg2, arg3, arg4, arg5);
+    return p->status;
+}
+
+
+/* 
+ * ZoomCmd --
+ *
+ *	Issue "move" commands for changes in the z-coordinate of the camera.
+ *	The problem here is that there is no event compression.  Consecutive
+ *	"zoom" commands are not compressed into a single directive.  The means
+ *	that the pymol server will render scenes that are not used by the
+ *	client.
+ *
+ *	Need to 1) defer the "move" commands until we find the next command
+ *	isn't a "zoom". 2) Track the z-coordinate as they are compressed.
+ */
+static int
+ZoomCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
+{
+    PymolProxy *p = clientData;
+    double factor = 0.0;
+    int defer = 0, push = 0, i, varg = 1;
+
+    clear_error(p);
+
+    for(i = 1; i < argc; i++) {
+	if (strcmp(argv[i],"-defer") == 0)
+	    defer = 1;
+	else if (strcmp(argv[i],"-push") == 0)
+	    push = 1;
+	else if (varg == 1) {
+	    double value;
+	    if (Tcl_GetDouble(interp, argv[i], &value) != TCL_OK) {
+		return TCL_ERROR;
+	    }
+	    factor = (float)value;
+	    varg++;
+	}
+    }
+    p->flags |= INVALIDATE_CACHE; /* Zoom */
+    if (!defer || push) {
+	p->flags |= UPDATE_PENDING;
+    }
+    if (push) {
+	p->flags |= FORCE_UPDATE;
+    }
+    if (factor != 0.0) {
+	p->zoom = factor;
+	p->flags |= ZOOM_PENDING;
+    }
+    return p->status;
+}
+
+        
+
+static int
+ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr) 
+{
+    struct timeval tv;
+    double start, finish;
+    int result;
+
+    gettimeofday(&tv, NULL);
+    start = CVT2SECS(tv);
+
+#if EXEC_DEBUG
+    DEBUG("command from client is (%s)", Tcl_DStringValue(dsPtr));
+#endif
+    result = Tcl_Eval(interp, Tcl_DStringValue(dsPtr));
+    if (result != TCL_OK) {
+#if EXEC_DEBUG
+	DEBUG("result was %s\n", Tcl_GetString(Tcl_GetObjResult(interp)));
+#endif
+    }
+    gettimeofday(&tv, NULL);
+    finish = CVT2SECS(tv);
+
+    stats.cmdTime += finish - start;
+    stats.numCommands++;
+    Tcl_DStringSetLength(dsPtr, 0);
+    return result;
+}
+
+static void
+SetViewport(PymolProxy *p)
+{
+    if (p->flags & VIEWPORT_PENDING) {
+	SendToPymol(p, "viewport %d,%d\n", p->width, p->height);
+	SendToPymol(p, "refresh\n");
+	p->flags &= ~VIEWPORT_PENDING;
+    }
+}
+
+static void
+SetZoom(PymolProxy *p)
+{
+    if (p->flags & ZOOM_PENDING) {
+        SendToPymol(p, "move z,%f\n", p->zoom);
+	p->flags &= ~ZOOM_PENDING;
+    }
+}
+
+static void
+SetPan(PymolProxy *p)
+{
+    if (p->flags & PAN_PENDING) {
+	SendToPymol(p, "move x,%f\nmove y,%f\n", p->xPan, p->yPan);
+	p->flags &= ~PAN_PENDING;
+    }
+}
+
+static void
+SetRotation(PymolProxy *p)
+{
+    if (p->flags & ROTATE_PENDING) {
+	/* Every pymol command line generates a new rendering. Execute all
+	 * three turns as a single command line. */
+	SendToPymol(p,"turn x,%f\nturn y,%f\nturn z,%f\n", p->xAngle, p->yAngle, 
+		p->zAngle);
+	p->xAngle = p->yAngle = p->zAngle = 0.0f;
+	p->flags &= ~ROTATE_PENDING;
+    }
+}
+
+static void
+SetSphereScale(PymolProxy *p)
+{
+    if (p->flags & ATOM_SCALE_PENDING) {
+	SendToPymol(p, "set sphere_scale,%f,all\n", p->sphereScale);
+	p->flags &= ~ATOM_SCALE_PENDING;
+    }
+}
+
+static void
+SetStickRadius(PymolProxy *p)
+{
+    if (p->flags & STICK_RADIUS_PENDING) {
+	SendToPymol(p, "set stick_radius,%f,all\n", p->stickRadius);
+	p->flags &= ~STICK_RADIUS_PENDING;
+    }
+}
+
+static void
+UpdateSettings(PymolProxy *p)
+{
+    /* Handle all the pending setting changes now. */
+    if (p->flags & VIEWPORT_PENDING) {
+	SetViewport(p);
+    }
+    if (p->flags & ROTATE_PENDING) {
+	SetRotation(p);
+    }
+    if (p->flags & PAN_PENDING) {
+	SetPan(p);
+    }
+    if (p->flags & ZOOM_PENDING) {
+	SetZoom(p);
+    }
+    if (p->flags & ATOM_SCALE_PENDING) {
+	SetSphereScale(p);
+    }
+    if (p->flags & STICK_RADIUS_PENDING) {
+	SetStickRadius(p);
+    }
+}
+
+static Image *
+NewImage(ImageList *listPtr, size_t dataLength)
+{
+    Image *imgPtr;
+    static int id = 0;
+
+    imgPtr = malloc(sizeof(Image) + dataLength);
+    if (imgPtr == NULL) {
+	ERROR("can't allocate image of %lu bytes", 
+	      (unsigned long)(sizeof(Image) + dataLength));
+	abort();
+    }
+    imgPtr->prevPtr = imgPtr->nextPtr = NULL;
+    imgPtr->bytesLeft = dataLength;
+    imgPtr->id = id++;
+#if WRITE_DEBUG
+    DEBUG("NewImage: allocating image %d of %d bytes", imgPtr->id, dataLength);
+#endif
+    if (listPtr->headPtr != NULL) {
+	listPtr->headPtr->prevPtr = imgPtr;
+    }
+    imgPtr->nextPtr = listPtr->headPtr;
+    if (listPtr->tailPtr == NULL) {
+	listPtr->tailPtr = imgPtr;
+    }
+    listPtr->headPtr = imgPtr;
+    imgPtr->numWritten = 0;
+    return imgPtr;
+}
+
+INLINE static void
+FreeImage(Image *imgPtr)
+{
+    assert(imgPtr != NULL);
+    free(imgPtr);
+}
+
+
+static void
+WriteImages(ImageList *listPtr, int fd)
+{
+    Image *imgPtr, *prevPtr; 
+
+    if (listPtr->tailPtr == NULL) {
+	ERROR("Should not be here: no image available to write");
+	return;
+    }
+#if WRITE_DEBUG
+	DEBUG("Entering WriteImages");
+#endif
+    for (imgPtr = listPtr->tailPtr; imgPtr != NULL; imgPtr = prevPtr) {
+	ssize_t bytesLeft;
+
+	assert(imgPtr->nextPtr == NULL);
+	prevPtr = imgPtr->prevPtr;
+#if WRITE_DEBUG
+	DEBUG("WriteImages: image %d of %d bytes.", imgPtr->id, 
+	      imgPtr->bytesLeft);
+#endif
+	for (bytesLeft = imgPtr->bytesLeft; bytesLeft > 0; /*empty*/) {
+	    ssize_t numWritten;
+#if WRITE_DEBUG
+	    DEBUG("image %d: bytesLeft=%d", imgPtr->id, bytesLeft);
+#endif
+	    numWritten = write(fd, imgPtr->data + imgPtr->numWritten, bytesLeft);
+#if WRITE_DEBUG
+	    DEBUG("image %d: wrote %d bytes.", imgPtr->id, numWritten);
+#endif
+	    if (numWritten < 0) {
+	        ERROR("Error writing fd=%d, %s", fd, strerror(errno));
+#if WRITE_DEBUG
+		DEBUG("Abnormal exit WriteImages");
+#endif
+		return;
+	    }
+	    bytesLeft -= numWritten;
+	    if (bytesLeft > 0) {
+#if WRITE_DEBUG
+		DEBUG("image %d, wrote a short buffer, %d bytes left.", 
+		      imgPtr->id, bytesLeft);
+#endif
+		/* Wrote a short buffer, means we would block. */
+		imgPtr->numWritten += numWritten;
+		imgPtr->bytesLeft = bytesLeft;
+#if WRITE_DEBUG
+		DEBUG("Abnormal exit WriteImages");
+#endif
+		return;
+	    }
+	    imgPtr->numWritten += numWritten;
+	}
+	/* Check if image is on the head.  */
+	listPtr->tailPtr = prevPtr;
+	if (prevPtr != NULL) {
+	    prevPtr->nextPtr = NULL;
+	}
+	FreeImage(imgPtr);
+    }
+    listPtr->headPtr = NULL;
+#if WRITE_DEBUG
+    DEBUG("Exit WriteImages");
+#endif
+}
+
+
+static void
+ChildHandler(int sigNum) 
+{
+    ERROR("pymol (%d) died unexpectedly", stats.pid);
+    pymolIsAlive = FALSE;
+    /*DoExit(1);*/
+}
+
+typedef struct {
+    const char *name;
+    Tcl_CmdProc *proc;
+} CmdProc;
+
+static CmdProc cmdProcs[] = {
+    { "cartoon",        CartoonCmd	  },       
+    { "cartoontrace",   CartoonTraceCmd	  },  
+    { "clientinfo",     ClientInfoCmd     }, 
+    { "disable",        DisableCmd	  },       
+    { "enable",         EnableCmd	  },        
+    { "frame",          FrameCmd          },         
+    { "label",          LabelCmd          },         
+    { "loadpdb",        LoadPDBCmd	  },       
+    { "orthoscopic",    OrthoscopicCmd	  },   
+    { "pan",            PanCmd		  },           
+    { "png",            PngCmd		  },           
+    { "ppm",            PpmCmd		  },           
+    { "print",          PrintCmd          },         
+    { "raw",            RawCmd		  },           
+    { "representation", RepresentationCmd },
+    { "reset",          ResetCmd          },         
+    { "rock",           RockCmd           },          
+    { "rotate",         RotateCmd         },        
+    { "screen",         ScreenCmd         },        
+    { "spherescale",    SphereScaleCmd    },   
+    { "stickradius",    StickRadiusCmd    },   
+    { "transparency",   TransparencyCmd   },  
+    { "viewport",       ScreenCmd         },        
+    { "vmouse",         VMouseCmd         },        
+    { "zoom",           ZoomCmd           },          
+    { NULL,	        NULL              }
+};
+
+static int
+InitProxy(PymolProxy *p, char *const *argv)
+{
+    int sin[2], sout[2];		/* Pipes to connect to server. */
+    pid_t child;
+    struct timeval end;
+
+#if DEBUG
+    DEBUG("Entering InitProxy\n");
+#endif
+    /* Create tow pipes for communication with the external application. One
+     * each for the applications's: stdin and stdout.  */
+
+    if (pipe(sin) == -1) {
+        return -1;
+    }
+    if (pipe(sout) == -1) {
+        close(sin[0]);
+        close(sin[1]);
+        return -1;
+    }
+
+    /* Fork the new process.  Connect I/O to the new socket.  */
+    child = fork();
+    if (child < 0) {
+        ERROR("can't fork process: %s\n", strerror(errno));
+        return -3;
+    }
+
+    if (child == 0) {			/* Child process */
+        int f;
+	char tmpname[200];
+
+        /* 
+         * Create a new process group, so we can later kill this process and
+         * all its children without affecting the process that created this
+         * one.
+         */
+        setpgid(child, 0); 
+        
+        /* Redirect stdin, stdout, and stderr to pipes before execing */ 
+
+        dup2(sin[0],  0);		/* Server standard input */
+        dup2(sout[1], 1);		/* Server standard output */
+
+	/* Redirect child's stdout to a log file. */
+	sprintf(tmpname, "%s/PYMOL-%d-stderr.XXXXXX", LOGDIR, getpid());
+	f = mkstemp(tmpname);
+	if (f < 0) {
+	    ERROR("can't open file `%s' to capture pymol stderr: %s", tmpname, 
+		  strerror(errno));
+	    exit(1);
+	}
+        dup2(f, 2);			/* Redirect stderr to a log file */
+        
+	/* Close all other descriptors  */        
+	for (f = 3; f < FD_SETSIZE; f++) {
+            close(f);
+	}
+        INFO("attempting to start \"%s\"", argv[0]);
+        execvp(argv[0], argv);
+        ERROR("can't exec `%s': %s", argv[0], strerror(errno));
+	exit(-1);
+    } else {
+	pymolIsAlive = TRUE;
+	signal(SIGCHLD, ChildHandler);
+    }
+    stats.pid = child;
+
+#if DEBUG
+    DEBUG("Started %s DISPLAY=%s\n", argv[0], getenv("DISPLAY"));
+#endif
+    /* close opposite end of pipe, these now belong to the child process  */
+    close(sin[0]);
+    close(sout[1]);
+
+    memset(p, 0, sizeof(PymolProxy));
+    p->sin        = sin[1];
+    p->sout       = sout[0];
+    p->cin        = fileno(stdout);
+    p->cout       = fileno(stdin);
+    p->flags      = CAN_UPDATE;
+    p->frame      = 1;
+    p->pid      = child;
+    InitReadBuffer(&p->client, "client", CLIENT_READ, 1<<16);
+    InitReadBuffer(&p->server, "server", p->sout, 1<<18);
+
+    /* Create safe interpreter and add pymol-specific commands to it. */
+    {
+	Tcl_Interp *interp;
+	CmdProc *cp;
+
+	interp = Tcl_CreateInterp();
+	Tcl_MakeSafe(interp);
+
+	for (cp = cmdProcs; cp->name != NULL; cp++) {
+#if DEBUG
+	    DEBUG("Adding command %s\n", cp->name);
+#endif
+	    Tcl_CreateCommand(interp, cp->name, cp->proc, p, NULL);
+	}
+	p->interp = interp;
+    }
+    gettimeofday(&end, NULL);
+    stats.start = end;
+    return 1;
+}
+
+static int
+FreeProxy(PymolProxy *p)
+{
+    int result, status;
+
+#if DEBUG
+    DEBUG("Enter FreeProxy");
+#endif
+    close(p->cout);
+    close(p->sout);
+    close(p->cin);
+    close(p->sin);
+
+    Tcl_DeleteInterp(p->interp);
+    ServerStats(0);
+
+#if DEBUG
+    DEBUG("Waiting for pymol server to exit");
+#endif
+    alarm(5);
+    if (waitpid(p->pid, &result, 0) < 0) {
+        ERROR("error waiting on pymol server to exit: %s", strerror(errno));
+    }
+#if DEBUG
+    DEBUG("attempting to signal (SIGTERM) pymol server.");
+#endif
+    kill(-p->pid, SIGTERM);		// Kill process group
+    alarm(5);
+    
+#if DEBUG
+    DEBUG("Waiting for pymol server to exit after SIGTERM");
+#endif
+    if (waitpid(p->pid, &result, 0) < 0) {
+	if (errno != ECHILD) {
+	    ERROR("error waiting on pymol server to exit after SIGTERM: %s", 
+		  strerror(errno));
+	}
+    }
+    status = -1;
+    while ((status == -1) && (errno == EINTR)) {
+#if DEBUG
+	DEBUG("Attempting to signal (SIGKILL) pymol server.");
+#endif
+	kill(-p->pid, SIGKILL);		// Kill process group
+	alarm(10);
+#if DEBUG
+	DEBUG("Waiting for pymol server to exit after SIGKILL");
+#endif
+	status = waitpid(p->pid, &result, 0);
+	alarm(0); 
+    }
+    INFO("pymol server process ended (result=%d)", result);
+
+    if (WIFEXITED(result)) {
+	result = WEXITSTATUS(result);
+    }
+    return result;
+}
+
+
+static void *
+ClientToServer(void *clientData)
+{
+    PymolProxy *p = clientData;
+    Tcl_DString command;
+    struct timeval tv, *tvPtr;
+
+#if READ_DEBUG
+    DEBUG("Reader thread started");
+#endif
+    Tcl_DStringInit(&command);
+    while (pymolIsAlive) {
+	tvPtr = NULL;
+#if READ_DEBUG
+	DEBUG("Start I/O set");
+#endif
+	while ((pymolIsAlive) && (WaitForNextLine(&p->client, tvPtr))) {
+	    size_t numBytes;
+	    const char *line;
+	    int status;
+
+	    status = GetLine(&p->client, &numBytes, &line);
+	    if (status != BUFFER_OK) {
+		ERROR("can't read client stdout (numBytes=%d): %s\n", numBytes,
+		      strerror(errno));
+		goto done;
+	    }
+	    Tcl_DStringAppend(&command, line, numBytes);
+	    if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
+		int result;
+		    
+		/* May execute more than one command. */
+		result = ExecuteCommand(p->interp, &command);
+		if (result == TCL_BREAK) {
+#if READ_DEBUG
+		    DEBUG("TCL_BREAK found");
+#endif
+		    break;		/* This was caused by a "imgflush"
+					 * command. Break out of the read
+					 * loop and allow a new image to be
+					 * rendered. */
+		}
+		if (p->flags & FORCE_UPDATE) {
+#if READ_DEBUG
+		    DEBUG("FORCE_UPDATE set");
+#endif
+		    break;
+		}
+	    }
+	    tv.tv_sec = 0L;
+	    tv.tv_usec = 0L;		/* On successive reads, we break
+					 * out if no data is available. */
+	    tvPtr = &tv;			
+	}
+#if READ_DEBUG
+	DEBUG("Finish I/O set");
+#endif
+	/* Handle all the pending setting changes now. */
+	UpdateSettings(p);
+
+	/* We might want to refresh the image if we're not currently
+	 * transmitting an image back to the client. The image will be
+	 * refreshed after the image has been completely transmitted. */
+	if (p->flags & UPDATE_PENDING) {
+#if READ_DEBUG
+	    DEBUG("calling ppm because of update");
+#endif
+	    Tcl_Eval(p->interp, "ppm");
+	    p->flags &= ~(UPDATE_PENDING|FORCE_UPDATE);
+	}
+    }
+ done:
+#if READ_DEBUG
+    DEBUG("Leaving Reader thread");
+#endif
+    return NULL;
+}
+
+static void *
+ServerToClient(void *clientData)
+{
+    ReadBuffer *bp = clientData;
+    ImageList list;
+
+#if WRITE_DEBUG
+    DEBUG("Writer thread started");
+#endif
+    list.headPtr = list.tailPtr = NULL;
+    while (pymolIsAlive) {
+	while (WaitForNextLine(bp, NULL)) {
+	    Image *imgPtr;
+	    const char *line;
+	    char header[200];
+	    size_t len;
+	    int numBytes;
+	    size_t hdrLength;
+	    int frameNum, rockOffset;
+	    char cacheId[200];
+
+	    /* Keep reading lines untils we find a "image follows:" line */
+	    if (GetLine(bp, &len, &line) != BUFFER_OK) {
+#if WRITE_DEBUG
+		DEBUG("Leaving Writer thread");
+#endif
+		return NULL;
+	    }
+#if WRITE_DEBUG
+	    DEBUG("Writer: line found is %s\n", line);
+#endif
+	    if (strncmp(line, "image follows: ", 15) != 0) {
+		continue;
+	    }
+	    if (sscanf(line, "image follows: %d %s %d %d\n", &numBytes, cacheId,
+		       &frameNum, &rockOffset) != 4) {
+		ERROR("can't get # bytes from \"%s\"", line);
+		DEBUG("Leaving Writer thread");
+		return NULL;
+	    } 
+#if WRITE_DEBUG
+	    DEBUG("found image line \"%.*s\"", numBytes, line);
+#endif
+	    sprintf(header, "nv>image %d %s %d %d\n", numBytes, cacheId, 
+		    frameNum, rockOffset);
+	    hdrLength = strlen(header);
+#if WRITE_DEBUG
+	    DEBUG("Queueing image numBytes=%d cacheId=%s, frameNum=%d, rockOffset=%d size=%d\n", numBytes, cacheId, frameNum, rockOffset, numBytes + hdrLength);
+#endif
+	    imgPtr = NewImage(&list, numBytes + hdrLength);
+	    memcpy(imgPtr->data, header, hdrLength);
+	    if (ReadFollowingData(bp, imgPtr->data + hdrLength, 
+			(size_t)numBytes) != BUFFER_OK) {
+		ERROR("can't read %d bytes for \"image follows\" buffer: %s", 
+		      numBytes, strerror(errno));
+#if WRITE_DEBUG
+		DEBUG("Leaving Writer thread");
+#endif
+		return NULL;
+	    }
+	    stats.numFrames++;
+	    stats.numBytes += numBytes;
+	    {
+		struct timeval tv;
+		fd_set writeFds;
+
+		tv.tv_sec = tv.tv_usec = 0L;
+		FD_ZERO(&writeFds);
+		FD_SET(CLIENT_WRITE, &writeFds);
+		if (select(CLIENT_WRITE+1, NULL, &writeFds, NULL, &tv) > 0) {
+		    WriteImages(&list, CLIENT_WRITE);
+		}
+	    }
+	}
+    }
+#if WRITE_DEBUG
+    DEBUG("Leaving Writer thread");
+#endif
+    return NULL;
+}
+
+int
+main(int argc, char **argv)
+{
+    PymolProxy proxy;
+    pthread_t thread1, thread2;
+    char version[200];
+    ssize_t numWritten;
+    size_t numBytes;
+
+    frecord = NULL;
+    if (recording) {
+	char fileName[200];
+
+	sprintf(fileName, "/tmp/pymolproxy%d.py", getpid());
+        frecord = fopen(fileName, "w");
+    }    
+    sprintf(version, "PymolProxy 1.0 (build %s)\n", SVN_VERSION);
+    numBytes = strlen(version);
+    numWritten = write(CLIENT_WRITE, version, numBytes);
+    if (numWritten < numBytes) {
+        ERROR("Short write on version string", strerror(errno));
+    }
+    INFO("Starting pymolproxy (threaded version)");
+
+    InitProxy(&proxy, argv + 1);
+    if (pthread_create(&thread1, NULL, &ClientToServer, &proxy) < 0) {
+        ERROR("Can't create reader thread: %s", strerror(errno));
+    }
+    if (pthread_create(&thread2, NULL, &ServerToClient, &proxy.server) < 0) {
+        ERROR("Can't create writer thread: %s", strerror(errno));
+    }
+    if (pthread_join(thread1, NULL) < 0) {
+        ERROR("Can't join reader thread: %s", strerror(errno));
+    } 
+    return FreeProxy(&proxy);
+}
Index: /pymolproxy/trunk/scripts/box.py
===================================================================
--- /pymolproxy/trunk/scripts/box.py	(revision 4588)
+++ /pymolproxy/trunk/scripts/box.py	(revision 4588)
@@ -0,0 +1,25 @@
+#
+# box.py -- create a Compiled Graphics Object for a unit cell box
+
+from pymol.cgo import *
+from pymol import cmd
+
+# Arguments: 
+#   lv: List of vertices; each vertex is a [x,y,z] list
+#   lw: Line width
+#    r: Red component of box color
+#    g: Green component of box color
+#    b: Blue component of box color
+# name: Name of box, which can be used with other PyMOL commands
+def draw_box(lv, lw=2.0, r=0.5, g=0.5, b=0.5, name='unitcell'):
+   box = [LINEWIDTH, lw, BEGIN, LINES, COLOR, r, g, b]
+   for v in lv:
+      box.append(VERTEX)
+      box.append(v[0])
+      box.append(v[1])
+      box.append(v[2])
+   box.append(END)
+   cmd.load_cgo(box, name)
+
+# Extend the PyMOL interpeter with the new "draw_box" command
+cmd.extend("draw_box", draw_box)
