source: trunk/packages/vizservers/pymolproxy/pymolproxy.c @ 2419

Last change on this file since 2419 was 2419, checked in by gah, 11 years ago
File size: 59.6 KB
Line 
1
2/*
3 * ----------------------------------------------------------------------
4 * proxypymol.c
5 *
6 *      This module creates the Tcl interface to the pymol server.  It acts as
7 *      a go-between establishing communication between a molvisviewer widget
8 *      and the pymol server. The communication protocol from the molvisviewer
9 *      widget is the Tcl language.  Commands are then relayed to the pymol
10 *      server.  Responses from the pymol server are translated into Tcl
11 *      commands and send to the molvisviewer widget. For example, resulting
12 *      image rendered offscreen is returned as BMP-formatted image data.
13 *
14 *  Copyright (c) 2004-2006  Purdue Research Foundation
15 *
16 *  See the file "license.terms" for information on usage and
17 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
18 * ======================================================================
19 */
20
21/*
22 * Notes:   The proxy should not maintain any state information from the
23 *          client, other that what it needs for event (rotate, pan, zoom,
24 *          atom scale, bond thickness, etc.) compression.  This is because
25 *          the connection is periodically broken (timeout, error, etc.).
26 *          It's the responsibility of the client (molvisviewer) to restore
27 *          the settings of the previous view.  The proxy is just a relay
28 *          between the client and the pymol server.
29 */
30/*
31 *   +--------------+        +------------+         +--------+
32 *   |              | output |            |  input  |        |
33 *   |              |------->|            |-------->|        |
34 *   | molvisviewer |        | pymolproxy |  output | pymol  |
35 *   |    client    |        |            |<--------| server |
36 *   |              | input  |            |  errors |        |
37 *   |              |<-------|            |<--------|        |
38 *   +--------------+        +------------+         +--------+
39 *
40 * The communication between the pymol server and proxy is completely
41 * synchronous.  The proxy relays/translates commands from the client to the
42 * server and then wait for the responses.  It also watches the server's
43 * stdout and stderr changes so that extraneous writes don't block the
44 * the server. (I don't know exactly what those responses are)
45 *
46 * The communication between the client and the proxy is different.  The
47 * client commands are read when they become available on the socket.  Proxy
48 * writes to the client (image output) are non-blocking so that we don't
49 * deadlock with the client.  The client may be busy sending the next command
50 * when we want to write the resulting image from the last command.
51 *
52 */
53
54#include <assert.h>
55#include <ctype.h>
56#include <errno.h>
57#include <fcntl.h>
58#include <getopt.h>
59#include <poll.h>
60#include <stdio.h>
61#include <stdlib.h>
62#include <string.h>
63#include <sys/stat.h>
64#include <sys/time.h>
65#include <sys/times.h>
66#include <sys/types.h>
67#include <sys/wait.h>
68#include <time.h>
69#include <syslog.h>
70#include <unistd.h>
71#include <tcl.h>
72
73#undef INLINE
74#ifdef __GNUC__
75#  define INLINE __inline__
76#else
77#  define INLINE
78#endif
79
80#define FALSE 0
81#define TRUE  1
82
83#define IO_TIMEOUT (30000)
84#define KEEPSTATS       1
85#define CVT2SECS(x)  ((double)(x).tv_sec) + ((double)(x).tv_usec * 1.0e-6)
86
87typedef struct {
88    pid_t child;                /* Child process. */
89    size_t nFrames;             /* # of frames sent to client. */
90    size_t nBytes;              /* # of bytes for all frames. */
91    size_t nCommands;           /* # of commands executed */
92    double cmdTime;             /* Elapsed time spend executing commands. */
93    struct timeval start;       /* Start of elapsed time. */
94} Stats;
95
96static Stats stats;
97
98#define READTRACE       0
99#define EXPECTTRACE     0
100#define WRITETRACE      0
101
102static int debug = FALSE;
103
104static FILE *flog;
105static FILE *scriptFile;
106static int savescript = FALSE;
107
108typedef struct Image {
109    struct Image *nextPtr;              /* Next image in chain of images. The
110                                         * list is ordered by the most
111                                         * recently received image from the
112                                         * pymol server to the least. */
113    struct Image *prevPtr;              /* Previous image in chain of
114                                         * images. The list is ordered by the
115                                         * most recently received image from
116                                         * the pymol server to the least. */
117    ssize_t nWritten;                   /* Number of bytes of image data
118                                         * already delivered.*/
119    size_t bytesLeft;                   /* Number of bytes of image data left
120                                         * to delivered to the client. */
121    char data[1];                       /* Start of image data. We allocate
122                                         * the size of the Image structure
123                                         * plus the size of the image data. */
124} Image;
125
126#define BUFFER_SIZE             4096
127
128typedef struct {
129    char bytes[BUFFER_SIZE];
130    int fill;
131    int mark;
132    int fd;
133} ReadBuffer;
134
135#define BUFFER_OK                0
136#define BUFFER_ERROR            -1
137#define BUFFER_CONTINUE         -2
138#define BUFFER_SHORT_READ       -3
139
140#define FORCE_UPDATE            (1<<0)
141#define CAN_UPDATE              (1<<1)
142#define INVALIDATE_CACHE        (1<<3)
143#define ATOM_SCALE_PENDING      (1<<4)
144#define STICK_RADIUS_PENDING    (1<<5)
145#define ROTATE_PENDING          (1<<6)
146#define PAN_PENDING             (1<<7)
147#define ZOOM_PENDING            (1<<8)
148#define UPDATE_PENDING          (1<<9)
149#define VIEWPORT_PENDING        (1<<10)
150
151typedef struct {
152    Tcl_Interp *interp;
153    unsigned int flags;                 /* Various flags. */
154    Image *headPtr, *tailPtr;           /* List of images to be delivered to
155                                         * the client.  The most recent images
156                                         * are in the front of the list. */
157
158    int sin, sout, serr;                /* Server file descriptors. */
159    int cin, cout;                      /* Client file descriptors. */
160    ReadBuffer client;                  /* Read buffer for client input. */
161    ReadBuffer server;                  /* Read buffer for server output. */
162    int frame;
163    int rockOffset;
164    int cacheId;
165    int error;
166    int status;
167    int width, height;                  /* Size of viewport. */
168    float xAngle, yAngle, zAngle;       /* Euler angles of pending
169                                         * rotation.  */
170    float sphereScale;                  /* Atom scale of pending re-scale. */
171    float stickRadius;                  /* Bond thickness of pending
172                                         * re-scale. */
173    float zoom;
174    float xPan, yPan;
175} PymolProxy;
176
177#define ERROR(...)      LogMessage(LOG_ERR, __FILE__, __LINE__, __VA_ARGS__)
178#define TRACE(...)      LogMessage(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
179#define WARN(...)       LogMessage(LOG_WARNING, __FILE__, __LINE__, __VA_ARGS__)
180#define INFO(...)       LogMessage(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
181
182static const char *syslogLevels[] = {
183    "emergency",                        /* System is unusable */
184    "alert",                            /* Action must be taken immediately */
185    "critical",                         /* Critical conditions */
186    "error",                            /* Error conditions */
187    "warning",                          /* Warning conditions */
188    "notice",                           /* Normal but significant condition */
189    "info",                             /* Informational */
190    "debug",                            /* Debug-level messages */
191};
192
193void
194LogMessage(int priority, const char *path, int lineNum, const char* fmt, ...)
195{
196#define MSG_LEN (2047)
197    char message[MSG_LEN+1];
198    const char *s;
199    int length;
200    va_list lst;
201
202    va_start(lst, fmt);
203    s = strrchr(path, '/');
204    if (s == NULL) {
205        s = path;
206    } else {
207        s++;
208    }
209    length = snprintf(message, MSG_LEN, "pymolproxy (%d) %s: %s:%d ",
210                      getpid(), syslogLevels[priority],  s, lineNum);
211    length += vsnprintf(message + length, MSG_LEN - length, fmt, lst);
212    message[MSG_LEN] = '\0';
213    if (debug) {
214        fprintf(stderr, "%s\n", message);
215    } else {
216        syslog(priority, message, length);
217    }
218}
219
220static void PollForEvents(PymolProxy *proxyPtr);
221static void
222Debug TCL_VARARGS_DEF(const char *, arg1)
223{
224    if (debug) {
225        const char *format;
226        va_list args;
227
228        format = TCL_VARARGS_START(const char *, arg1, args);
229        fprintf(flog, "pymolproxy: ");
230        vfprintf(flog, format, args);
231        fprintf(flog, "\n");
232        fflush(flog);
233    }
234}
235
236static void
237script TCL_VARARGS_DEF(const char *, arg1)
238{
239    if (savescript) {
240        const char *format;
241        va_list args;
242
243        format = TCL_VARARGS_START(const char *, arg1, args);
244        vfprintf(scriptFile, format, args);
245        fflush(scriptFile);
246    }
247}
248
249static void
250InitBuffer(ReadBuffer *readPtr, int f)
251{
252    readPtr->fd = f;
253    readPtr->fill = 0;
254    readPtr->mark = 0;
255}
256
257static void
258FlushBuffer(ReadBuffer *readPtr)
259{
260    readPtr->fill = 0;
261    readPtr->mark = 0;
262}
263
264static int
265FillBuffer(ReadBuffer *readPtr)
266{
267    size_t bytesLeft;
268    ssize_t nRead;
269
270#if READTRACE
271    Debug("Entering FillBuffer (mark=%d, fill=%d)\n", readPtr->mark,
272          readPtr->fill);
273#endif
274    if (readPtr->mark >= readPtr->fill) {
275        readPtr->mark = readPtr->fill = 0;
276    }
277    if (readPtr->mark > 0) {
278        size_t i, j;
279
280        for (i = 0, j = readPtr->mark; j < readPtr->fill; i++, j++) {
281            readPtr->bytes[i] = readPtr->bytes[j];
282        }
283        readPtr->mark = 0;
284        readPtr->fill = i;
285    }
286    bytesLeft = BUFFER_SIZE - readPtr->fill - 1;
287    nRead = read(readPtr->fd, readPtr->bytes + readPtr->fill, bytesLeft);
288    if (nRead == 0) {
289        return BUFFER_ERROR;
290    }
291    if (nRead <= 0) {
292        if (errno != EAGAIN) {
293#if READTRACE
294            Debug("in FillBuffer: read failed %d: %s", errno, strerror(errno));
295            Debug("Leaving FillBuffer FAIL(read %d bytes) mark=%d, fill=%d\n",
296                  nRead, readPtr->mark, readPtr->fill);
297#endif
298            return BUFFER_ERROR;
299        }
300        return BUFFER_SHORT_READ;
301    }
302    readPtr->fill += nRead;
303#if READTRACE
304    Debug("Leaving FillBuffer (read %d bytes) mark=%d, fill=%d\n",
305          nRead, readPtr->mark, readPtr->fill);
306#endif
307    return (nRead == bytesLeft) ? BUFFER_OK : BUFFER_SHORT_READ;
308}
309
310static char *
311GetLine(ReadBuffer *readPtr, int *nBytesPtr)
312{
313    int i;
314    int status;
315
316#if READTRACE
317    Debug("Entering GetLine (mark=%d, fill=%d)\n",readPtr->mark, readPtr->fill);
318#endif
319    status = BUFFER_OK;
320    for (;;) {
321        /* Look for the next newline (the next full line). */
322#if READTRACE
323        Debug("in GetLine: mark=%d fill=%d\n", readPtr->mark, readPtr->fill);
324#endif
325        for (i = readPtr->mark; i < readPtr->fill; i++) {
326            if (readPtr->bytes[i] == '\n') {
327                char *p;
328               
329                /* Save the start of the line. */
330                p = readPtr->bytes + readPtr->mark;
331                i++;
332                *nBytesPtr = i - readPtr->mark;
333                readPtr->mark = i;
334#if READTRACE
335                Debug("Leaving GetLine(%.*s)\n", *nBytesPtr, p);
336#endif
337                return p;
338            }
339        }
340        /* Couldn't find a newline, so it may be that we need to read some
341         * more. Check first that last read wasn't a short read. */
342        if (status == BUFFER_SHORT_READ) {
343            break;
344        }
345        status = FillBuffer(readPtr);
346        if (status == BUFFER_ERROR) {
347            *nBytesPtr = BUFFER_ERROR;
348            return NULL;        /* EOF or error on read. */
349        }
350    }
351#if READTRACE
352    Debug("Leaving GetLine failed to read line\n");
353#endif
354    *nBytesPtr = BUFFER_CONTINUE;
355    return NULL;
356}
357
358static int
359GetBytes(ReadBuffer *readPtr, char *out, int nBytes)
360{
361#if READTRACE
362    Debug("Entering GetBytes(%d)\n", nBytes);
363#endif
364    while (nBytes > 0) {
365        int bytesLeft;
366        int status;
367
368        bytesLeft = readPtr->fill - readPtr->mark;
369        if (bytesLeft > 0) {
370            int size;
371
372            size = (bytesLeft >  nBytes) ? nBytes : bytesLeft;
373            memcpy(out, readPtr->bytes + readPtr->mark, size);
374            readPtr->mark += size;
375            nBytes -= size;
376            out += size;
377        }
378        if (nBytes == 0) {
379            /* Received requested # bytes. */
380#if READTRACE
381            Debug("Leaving GetBytes(%d)\n", nBytes);
382#endif
383            return BUFFER_OK;
384        }
385        /* Didn't get enough bytes, need to read some more. */
386        status = FillBuffer(readPtr);
387        if (status == BUFFER_ERROR) {
388            return BUFFER_ERROR;
389        }
390#if READTRACE
391        Debug("in GetBytes: mark=%d fill=%d\n", readPtr->mark, readPtr->fill);
392#endif
393    }
394#if READTRACE
395    Debug("Leaving GetBytes(%d)\n", nBytes);
396#endif
397    return BUFFER_OK;
398}
399
400INLINE static void
401clear_error(PymolProxy *proxyPtr)
402{
403    proxyPtr->error = 0;
404    proxyPtr->status = TCL_OK;
405}
406
407static int
408CreateTmpDir(Tcl_Interp *interp)
409{
410    const char script[] = {
411        "set path \"/tmp/pymol[pid]\"\n"
412        "if { [file exists $path] } {\n"
413        "    file delete -force $path\n"
414        "}\n"
415        "file mkdir $path\n"
416    };
417    return Tcl_GlobalEval(interp, script);
418}
419
420static void
421DestroyTmpDir()
422{
423    char cmd[BUFSIZ];
424
425    sprintf(cmd, "/bin/rm -rf /tmp/pymol%d", getpid());
426    if (system(cmd) < 0) {
427        ERROR("can't delete tmp directory: %s\n", strerror(errno));
428    }
429}
430
431static int
432Expect(PymolProxy *proxyPtr, char *match, char *out, int maxSize)
433{
434    char c;
435    size_t length;
436
437    if (proxyPtr->status != TCL_OK) {
438        return proxyPtr->status;
439    }
440#if EXPECTTRACE
441    Debug("Entering Expect(want=\"%s\", maxSize=%d)\n", match, maxSize);
442#endif
443    c = match[0];
444    length = strlen(match);
445    for (;;) {
446        int nBytes;
447        char *line;
448
449        line = GetLine(&proxyPtr->server, &nBytes);
450        if (line != NULL) {
451#if EXPECTTRACE
452            Debug("pymol says (read %d bytes):%.*s", nBytes, nBytes, line);
453#endif
454            if ((c == line[0]) && (strncmp(line, match, length) == 0)) {
455                if (maxSize < nBytes) {
456                    nBytes = maxSize;
457                }
458                memcpy(out, line, nBytes);
459                clear_error(proxyPtr);
460#if EXPECTTRACE
461                Debug("Leaving Expect: got (%.*s)\n", nBytes, out);
462#endif
463                return BUFFER_OK;
464            }
465            continue;
466        }
467        if (nBytes != BUFFER_CONTINUE) {
468            return BUFFER_ERROR;
469        }
470    }
471    ERROR("Leaving Expect: failed to find (%s)\n", match);
472    proxyPtr->error = 2;
473    proxyPtr->status = TCL_ERROR;
474    return BUFFER_ERROR;
475}
476
477#if KEEPSTATS
478
479static int
480WriteStats(const char *who, int code)
481{
482    double start, finish;
483    pid_t pid;
484    char buf[BUFSIZ];
485    Tcl_DString ds;
486
487    {
488        struct timeval tv;
489
490        /* Get ending time.  */
491        gettimeofday(&tv, NULL);
492        finish = CVT2SECS(tv);
493        tv = stats.start;
494        start = CVT2SECS(tv);
495    }
496    /*
497     * Session information:
498     *   1. Start date of session in seconds.
499     *   2. Process ID
500     *   3. Number of frames returned.
501     *   4. Number of bytes total returned (in frames).
502     *   5. Total elapsed time of all commands.
503     *   6. Total elapsed time of session.
504     *   7. Exit code of pymol server.
505     *   8. User time. 
506     *   9. System time.
507     *  10. Maximum resident size.
508     */
509    pid = getpid();
510    Tcl_DStringInit(&ds);
511   
512    sprintf(buf, "<session server=\"%s\" ", who);
513    Tcl_DStringAppend(&ds, buf, -1);
514
515    strcpy(buf, ctime(&stats.start.tv_sec));
516
517    buf[strlen(buf) - 1] = '\0';
518    Tcl_DStringAppend(&ds, "date=\"", -1);
519    Tcl_DStringAppend(&ds, buf, -1);
520    Tcl_DStringAppend(&ds, "\" ", -1);
521
522    sprintf(buf, "date_secs=\"%ld\" ", stats.start.tv_sec);
523    Tcl_DStringAppend(&ds, buf, -1);
524
525    sprintf(buf, "pid=\"%d\" ", pid);
526    Tcl_DStringAppend(&ds, buf, -1);
527    sprintf(buf, "num_frames=\"%lu\" ", (unsigned long int)stats.nFrames);
528    Tcl_DStringAppend(&ds, buf, -1);
529    sprintf(buf, "frame_bytes=\"%lu\" ", (unsigned long int)stats.nBytes);
530    Tcl_DStringAppend(&ds, buf, -1);
531    sprintf(buf, "num_commands=\"%lu\" ", (unsigned long int)stats.nCommands);
532    Tcl_DStringAppend(&ds, buf, -1);
533    sprintf(buf, "cmd_time=\"%g\" ", stats.cmdTime);
534    Tcl_DStringAppend(&ds, buf, -1);
535    sprintf(buf, "session_time=\"%g\" ", finish - start);
536    Tcl_DStringAppend(&ds, buf, -1);
537    sprintf(buf, "status=\"%d\" ", code);
538    Tcl_DStringAppend(&ds, buf, -1);
539    {
540        long clocksPerSec = sysconf(_SC_CLK_TCK);
541        double clockRes = 1.0 / clocksPerSec;
542        struct tms tms;
543
544        memset(&tms, 0, sizeof(tms));
545        times(&tms);
546        sprintf(buf, "utime=\"%g\" ", tms.tms_utime * clockRes);
547        Tcl_DStringAppend(&ds, buf, -1);
548        sprintf(buf, "stime=\"%g\" ", tms.tms_stime * clockRes);
549        Tcl_DStringAppend(&ds, buf, -1);
550        sprintf(buf, "cutime=\"%g\" ", tms.tms_cutime * clockRes);
551        Tcl_DStringAppend(&ds, buf, -1);
552        sprintf(buf, "cstime=\"%g\" ", tms.tms_cstime * clockRes);
553        Tcl_DStringAppend(&ds, buf, -1);
554    }
555    Tcl_DStringAppend(&ds, "/>\n", -1);
556
557    {
558        int f;
559        ssize_t length;
560        int result;
561
562#define STATSDIR        "/var/tmp/visservers"
563#define STATSFILE       STATSDIR "/" "data.xml"
564        if (access(STATSDIR, X_OK) != 0) {
565            mkdir(STATSDIR, 0770);
566        }
567        length = Tcl_DStringLength(&ds);
568        f = open(STATSFILE, O_APPEND | O_CREAT | O_WRONLY, 0600);
569        result = FALSE;
570        if (f < 0) {
571            goto error;
572        }
573        if (write(f, Tcl_DStringValue(&ds), length) != length) {
574            goto error;
575        }
576        result = TRUE;
577 error:
578        if (f >= 0) {
579            close(f);
580        }
581        Tcl_DStringFree(&ds);
582        return result;
583    }
584}
585#endif
586
587static void
588DoExit(int code)
589{
590#if KEEPSTATS
591    WriteStats("pymolproxy", code);
592#endif
593   
594    exit(code);
595}
596
597static int
598ExecuteCommand(Tcl_Interp *interp, const char *cmd)
599{
600    struct timeval tv;
601    double start, finish;
602    int result;
603
604    gettimeofday(&tv, NULL);
605    start = CVT2SECS(tv);
606
607    Debug("command from client is (%s)", cmd);
608    result = Tcl_Eval(interp, cmd);
609
610    gettimeofday(&tv, NULL);
611    finish = CVT2SECS(tv);
612
613    stats.cmdTime += finish - start;
614    stats.nCommands++;
615    return result;
616}
617
618static Image *
619NewImage(PymolProxy *proxyPtr, size_t dataLength)
620{
621    Image *imgPtr;
622
623    imgPtr = malloc(sizeof(Image) + dataLength);
624    if (imgPtr == NULL) {
625        ERROR("can't allocate image of %lu bytes",
626              (unsigned long)(sizeof(Image) + dataLength));
627        abort();
628    }
629    imgPtr->prevPtr = imgPtr->nextPtr = NULL;
630    imgPtr->bytesLeft = dataLength;
631    if (proxyPtr->headPtr != NULL) {
632        proxyPtr->headPtr->prevPtr = imgPtr;
633    }
634    imgPtr->nextPtr = proxyPtr->headPtr;
635    if (proxyPtr->tailPtr == NULL) {
636        proxyPtr->tailPtr = imgPtr;
637    }
638    proxyPtr->headPtr = imgPtr;
639    imgPtr->nWritten = 0;
640    return imgPtr;
641}
642
643INLINE static void
644FreeImage(Image *imgPtr)
645{
646    assert(imgPtr != NULL);
647    free(imgPtr);
648}
649
650static void
651WriteImage(PymolProxy *proxyPtr, int fd)
652{
653    Image *imgPtr, *prevPtr;
654
655    if (proxyPtr->tailPtr == NULL) {
656        ERROR("Should not be here: no image available to write");
657        return;
658    }
659    for (imgPtr = proxyPtr->tailPtr; imgPtr != NULL; imgPtr = prevPtr) {
660        ssize_t bytesLeft;
661
662        assert(imgPtr->nextPtr == NULL);
663        prevPtr = imgPtr->prevPtr;
664#if WRITETRACE
665        Debug("WriteImage: want to write %d bytes.", imgPtr->bytesLeft);
666#endif
667        for (bytesLeft = imgPtr->bytesLeft; bytesLeft > 0; /*empty*/) {
668            ssize_t nWritten;
669#if WRITETRACE
670            Debug("WriteImage: try to write %d bytes.", bytesLeft);
671#endif
672            nWritten = write(fd, imgPtr->data + imgPtr->nWritten, bytesLeft);
673#if WRITETRACE
674            Debug("WriteImage: wrote %d bytes.", nWritten);
675#endif
676            if (nWritten < 0) {
677                ERROR("Error writing fd(%d), %d/%s.", fd, errno,
678                      strerror(errno));
679                return;
680            }
681            bytesLeft -= nWritten;
682            if (bytesLeft > 0) {
683                /* Wrote a short buffer, means we would block. */
684                imgPtr->nWritten += nWritten;
685                imgPtr->bytesLeft = bytesLeft;
686                return;
687            }
688            imgPtr->nWritten += nWritten;
689        }
690        /* Check if image is on the head.  */
691        proxyPtr->tailPtr = prevPtr;
692        if (prevPtr != NULL) {
693            prevPtr->nextPtr = NULL;
694        }
695        FreeImage(imgPtr);
696    }
697    proxyPtr->headPtr = NULL;
698}
699
700static int
701Pymol(PymolProxy *proxyPtr, const char *format, ...)
702{
703    va_list ap;
704    char buffer[BUFSIZ];
705    char expect[BUFSIZ];
706    int result;
707    ssize_t nWritten;
708    size_t length;
709    char *p;
710
711    if (proxyPtr->error) {
712        return TCL_ERROR;
713    }
714   
715    va_start(ap, format);
716    result = vsnprintf(buffer, BUFSIZ-1, format, ap);
717    va_end(ap);
718   
719    Debug("to-pymol>(%s) code=%d", buffer, result);
720    script("%s\n", buffer);
721   
722   
723    /* Write the command out to the server. */
724    length = strlen(buffer);
725    nWritten = write(proxyPtr->sin, buffer, length);
726    if (nWritten != length) {
727        ERROR("short write to pymol (wrote=%d, should have been %d)",
728              nWritten, length);
729    }
730    for (p = buffer; *p != '\0'; p++) {
731        if (*p == '\n') {
732            *p = '\0';
733            break;
734        }
735    }
736    sprintf(expect, "PyMOL>%s", buffer);
737    /* Now wait for the "PyMOL>" prompt. */
738    result = Expect(proxyPtr, expect, buffer, BUFSIZ);
739    if (result == BUFFER_ERROR) {
740        ERROR("timeout reading data (buffer=%s)", buffer);
741        proxyPtr->error = 1;
742        proxyPtr->status = TCL_ERROR;
743        return proxyPtr->status;
744    }
745    return  proxyPtr->status;
746}
747
748static void
749SetViewport(PymolProxy *proxyPtr)
750{
751    if (proxyPtr->flags & VIEWPORT_PENDING) {
752        Pymol(proxyPtr, "viewport %d,%d\n", proxyPtr->width, proxyPtr->height);
753        proxyPtr->flags &= ~VIEWPORT_PENDING;
754    }
755}
756
757static void
758SetZoom(PymolProxy *proxyPtr)
759{
760    if (proxyPtr->flags & ZOOM_PENDING) {
761        Pymol(proxyPtr,"move z,%f\n", proxyPtr->zoom);
762        proxyPtr->flags &= ~ZOOM_PENDING;
763    }
764}
765
766static void
767SetPan(PymolProxy *proxyPtr)
768{
769    if (proxyPtr->flags & PAN_PENDING) {
770        Pymol(proxyPtr,"move x,%f\nmove y,%f\n", proxyPtr->xPan,proxyPtr->yPan);
771        proxyPtr->flags &= ~PAN_PENDING;
772    }
773}
774
775static void
776SetRotation(PymolProxy *proxyPtr)
777{
778    if (proxyPtr->flags & ROTATE_PENDING) {
779        /* Every pymol command line generates a new rendering. Execute all
780         * three turns as a single command line. */
781        Pymol(proxyPtr,"turn x,%f\nturn y,%f\nturn z,%f\n",
782              proxyPtr->xAngle, proxyPtr->yAngle, proxyPtr->zAngle);
783        proxyPtr->xAngle = proxyPtr->yAngle = proxyPtr->zAngle = 0.0f;
784        proxyPtr->flags &= ~ROTATE_PENDING;
785    }
786}
787
788static void
789SetSphereScale(PymolProxy *proxyPtr)
790{
791    if (proxyPtr->flags & ATOM_SCALE_PENDING) {
792        Pymol(proxyPtr, "set sphere_scale,%f,all\n", proxyPtr->sphereScale);
793        proxyPtr->flags &= ~ATOM_SCALE_PENDING;
794    }
795}
796
797static void
798SetStickRadius(PymolProxy *proxyPtr)
799{
800    if (proxyPtr->flags & STICK_RADIUS_PENDING) {
801        Pymol(proxyPtr, "set stick_radius,%f,all\n", proxyPtr->stickRadius);
802        proxyPtr->flags &= ~STICK_RADIUS_PENDING;
803    }
804}
805
806static void
807UpdateSettings(PymolProxy *proxyPtr)
808{
809    /* Handle all the pending setting changes now. */
810    if (proxyPtr->flags & VIEWPORT_PENDING) {
811        SetViewport(proxyPtr);
812    }
813    if (proxyPtr->flags & ROTATE_PENDING) {
814        SetRotation(proxyPtr);
815    }
816    if (proxyPtr->flags & PAN_PENDING) {
817        SetPan(proxyPtr);
818    }
819    if (proxyPtr->flags & ZOOM_PENDING) {
820        SetZoom(proxyPtr);
821    }
822    if (proxyPtr->flags & ATOM_SCALE_PENDING) {
823        SetSphereScale(proxyPtr);
824    }
825    if (proxyPtr->flags & STICK_RADIUS_PENDING) {
826        SetStickRadius(proxyPtr);
827    }
828}
829
830
831static int
832BmpCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
833{
834    char buffer[BUFSIZ];
835    unsigned int nBytes=0;
836    PymolProxy *proxyPtr = clientData;
837    Image *imgPtr;
838    size_t length;
839    clear_error(proxyPtr);
840
841    if (proxyPtr->flags & INVALIDATE_CACHE)
842        proxyPtr->cacheId++;
843
844    proxyPtr->flags &= ~(UPDATE_PENDING|FORCE_UPDATE|INVALIDATE_CACHE);
845
846    /* Force pymol to update the current scene. */
847    UpdateSettings(proxyPtr);
848
849    Pymol(proxyPtr, "refresh\n");
850    Pymol(proxyPtr, "bmp -\n");
851    if (Expect(proxyPtr, "bmp image follows: ", buffer, BUFSIZ) != BUFFER_OK) {
852        ERROR("can't find image follows line (%s)", buffer);
853    }
854    if (sscanf(buffer, "bmp image follows: %d\n", &nBytes) != 1) {
855        Tcl_AppendResult(interp, "can't get # bytes from \"", buffer, "\"",
856                         (char *)NULL);
857        return TCL_ERROR;
858    }
859    sprintf(buffer, "nv>image %d %d %d %d\n", nBytes, proxyPtr->cacheId,
860            proxyPtr->frame, proxyPtr->rockOffset);
861
862    length = strlen(buffer);
863    imgPtr = NewImage(proxyPtr, nBytes + length);
864    strcpy(imgPtr->data, buffer);
865    if (GetBytes(&proxyPtr->server, imgPtr->data + length, nBytes)!=BUFFER_OK){
866        ERROR("can't read %d bytes for \"image follows\" buffer", nBytes);
867        return  TCL_ERROR;
868    }
869    stats.nFrames++;
870    stats.nBytes += nBytes;
871    return proxyPtr->status;
872}
873
874static int
875CartoonCmd(ClientData clientData, Tcl_Interp *interp, int argc,
876           const char *argv[])
877{
878    int bool, defer, push, i;
879    PymolProxy *proxyPtr = clientData;
880    const char *model;
881
882    clear_error(proxyPtr);
883    defer = push = FALSE;
884    model = "all";
885    bool = FALSE;
886    for(i = 1; i < argc; i++) {
887        if (strcmp(argv[i],"-defer") == 0) {
888            defer = TRUE;
889        } else if (strcmp(argv[i],"-push") == 0) {
890            push = TRUE;
891        } else if (strcmp(argv[i],"-model") == 0) {
892            if (++i < argc) {
893                model = argv[i];
894            }
895        } else {
896            if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
897                return TCL_ERROR;
898            }
899        }
900    }
901    proxyPtr->flags |= INVALIDATE_CACHE; /* cartoon */
902    if (!defer || push) {
903        proxyPtr->flags |= UPDATE_PENDING;
904    }
905    if (push) {
906        proxyPtr->flags |= FORCE_UPDATE;
907    }
908    if (bool) {
909        Pymol(proxyPtr, "show cartoon,%s\n", model);
910    } else {
911        Pymol(proxyPtr, "hide cartoon,%s\n", model);
912    }
913    return proxyPtr->status;
914}
915
916static int
917CartoonTraceCmd(ClientData clientData, Tcl_Interp *interp, int argc,
918                const char *argv[])
919{
920    int bool, defer, push, i;
921    PymolProxy *proxyPtr = clientData;
922    const char *model;
923
924    clear_error(proxyPtr);
925    defer = push = FALSE;
926    bool = FALSE;
927    model = "all";
928    for(i = 1; i < argc; i++) {
929        if (strcmp(argv[i],"-defer") == 0) {
930            defer = TRUE;
931        } else if (strcmp(argv[i],"-push") == 0) {
932            push = TRUE;
933        } else if (strcmp(argv[i],"-model") == 0) {
934            if (++i < argc) {
935                model = argv[i];
936            }
937        } else {
938            if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
939                return TCL_ERROR;
940            }
941        }
942    }
943    proxyPtr->flags |= INVALIDATE_CACHE; /* cartoon_trace  */
944    if (!defer || push) {
945        proxyPtr->flags |= UPDATE_PENDING;
946    }
947    if (push) {
948        proxyPtr->flags |= FORCE_UPDATE;
949    }
950    Pymol(proxyPtr, "set cartoon_trace,%d,%s\n", bool, model);
951    return proxyPtr->status;
952}
953
954static int
955DisableCmd(ClientData clientData, Tcl_Interp *interp, int argc,
956           const char *argv[])
957{
958    PymolProxy *proxyPtr = clientData;
959    const char *model = "all";
960    int i, defer = 0, push = 0;
961
962    clear_error(proxyPtr);
963
964    for(i = 1; i < argc; i++) {
965
966        if (strcmp(argv[i], "-defer") == 0 )
967            defer = 1;
968        else if (strcmp(argv[i], "-push") == 0 )
969            push = 1;
970        else
971            model = argv[i];
972       
973    }
974
975    proxyPtr->flags |= INVALIDATE_CACHE;  /* disable */
976    if (!defer || push) {
977        proxyPtr->flags |= UPDATE_PENDING;
978    }
979    if (push) {
980        proxyPtr->flags |= FORCE_UPDATE;
981    }
982
983    Pymol( proxyPtr, "disable %s\n", model);
984
985    return proxyPtr->status;
986}
987
988
989static int
990EnableCmd(ClientData clientData, Tcl_Interp *interp, int argc,
991          const char *argv[])
992{
993    PymolProxy *proxyPtr = clientData;
994    const char *model;
995    int i, defer, push;
996
997    clear_error(proxyPtr);
998    push = defer = FALSE;
999    model = "all";
1000    for(i = 1; i < argc; i++) {
1001        if (strcmp(argv[i],"-defer") == 0) {
1002            defer = TRUE;
1003        } else if (strcmp(argv[i], "-push") == 0) {
1004            push = TRUE;
1005        } else {
1006            model = argv[i];
1007        }
1008    }
1009    proxyPtr->flags |= INVALIDATE_CACHE; /* enable */
1010    if (!defer || push) {
1011        proxyPtr->flags |= UPDATE_PENDING;
1012    }
1013    if (push) {
1014        proxyPtr->flags |= FORCE_UPDATE;
1015    }
1016    Pymol( proxyPtr, "enable %s\n", model);
1017    return proxyPtr->status;
1018}
1019
1020static int
1021FrameCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1022         const char *argv[])
1023{
1024    PymolProxy *proxyPtr = clientData;
1025    int i, push, defer, frame;
1026
1027    clear_error(proxyPtr);
1028    frame = 0;
1029    push = defer = TRUE;
1030    for(i = 1; i < argc; i++) {
1031        if (strcmp(argv[i],"-defer") == 0) {
1032            defer = TRUE;
1033        } else if (strcmp(argv[i],"-push") == 0) {
1034            push = TRUE;
1035        } else {
1036            frame = atoi(argv[i]);
1037        }
1038    }
1039    if (!defer || push) {
1040        proxyPtr->flags |= UPDATE_PENDING;
1041    }
1042    if (push) {
1043        proxyPtr->flags |= FORCE_UPDATE;
1044    }
1045    proxyPtr->frame = frame;
1046
1047    /* Does not invalidate cache? */
1048
1049    Pymol(proxyPtr,"frame %d\n", frame);
1050    return proxyPtr->status;
1051}
1052
1053
1054static int
1055LabelCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1056         const char *argv[])
1057{
1058    PymolProxy *proxyPtr = clientData;
1059    int i, push, defer, state, size;
1060    const char *model;
1061
1062    clear_error(proxyPtr);
1063    model = "all";
1064    size = 14;
1065    state = TRUE;
1066    push = defer = FALSE;
1067    for(i = 1; i < argc; i++) {
1068        if (strcmp(argv[i],"-defer") == 0) {
1069            defer = TRUE;
1070        } else if (strcmp(argv[i],"-push") == 0) {
1071            push = TRUE;
1072        } else if (strcmp(argv[i],"-model") == 0) {
1073            if (++i < argc) {
1074                model = argv[i];
1075            }
1076        } else if (strcmp(argv[i],"-size") == 0) {
1077            if (++i < argc) {
1078                size = atoi(argv[i]);
1079            }
1080        } else if (Tcl_GetBoolean(interp, argv[i], &state) != TCL_OK) {
1081            return TCL_ERROR;
1082        }
1083    }
1084    proxyPtr->flags |= INVALIDATE_CACHE;  /* label */
1085    if (!defer || push) {
1086        proxyPtr->flags |= UPDATE_PENDING;
1087    }
1088    if (push) {
1089        proxyPtr->flags |= FORCE_UPDATE;
1090    }
1091    Pymol(proxyPtr,
1092          "set label_color,white,%s\n"
1093          "set label_size,%d,%s\n",
1094          model, size, model);
1095    if (state) {
1096        Pymol(proxyPtr, "label %s,\"%%s%%s\" %% (ID,name)\n", model);
1097    } else {
1098        Pymol(proxyPtr, "label %s\n", model);
1099    }
1100    return proxyPtr->status;
1101}
1102
1103/*
1104 * LoadPDBCmd --
1105 *
1106 *      Load a PDB file into pymol.  There is no good way to load PDB data
1107 *      into pymol without using a file.  This causes problems in that we
1108 *      don't know when pymol is done with the file.  So there's always
1109 *      a bit of mess left around. We use the same file for every file
1110 *      so there's a chance that pymol is still using a file while we're
1111 *      overwriting it. 
1112 *
1113 *      The command expects the number of bytes in the pdb data, the
1114 *      name, and the state.
1115 */
1116static int
1117LoadPDBCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1118           const char *argv[])
1119{
1120    const char *data, *name;
1121    char *allocated;
1122    PymolProxy *proxyPtr = clientData;
1123    int state, defer, push;
1124    int nBytes;
1125    int i, j;
1126
1127    if (proxyPtr == NULL){
1128        return TCL_ERROR;
1129    }
1130    clear_error(proxyPtr);
1131    defer = push = FALSE;
1132    for(i = j = 1; i < argc; i++) {
1133        if (strcmp(argv[i],"-defer") == 0) {
1134            defer = TRUE;
1135        } else if (strcmp(argv[i],"-push") == 0) {
1136            push = TRUE;
1137        } else {
1138            if (j < i) {
1139                argv[j] = argv[i];
1140            }
1141            j++;
1142        }
1143    }
1144    argc = j;
1145    if (argc < 4) {
1146        Tcl_AppendResult(interp, "wrong # arguments: should be \"", argv[0],
1147                         " <data>|follows <model> <state> ?<nBytes>?\"",
1148                         (char *)NULL);
1149        return TCL_ERROR;
1150    }
1151    data = argv[1];
1152    name = argv[2];
1153    if (Tcl_GetInt(interp, argv[3], &state) != TCL_OK) {
1154        return TCL_ERROR;
1155    }
1156    nBytes = -1;
1157    if (strcmp(data, "follows") == 0) {
1158        if (argc != 5) {
1159            Tcl_AppendResult(interp, "wrong # arguments: should be \"", argv[0],
1160                         " follows <model> <state> <nBytes>\"", (char *)NULL);
1161            return TCL_ERROR;
1162        }
1163        if (Tcl_GetInt(interp, argv[4], &nBytes) != TCL_OK) {
1164            return TCL_ERROR;
1165        }
1166        if (nBytes < 0) {
1167            Tcl_AppendResult(interp, "bad value for # bytes \"", argv[4],
1168                         "\"", (char *)NULL);
1169            return TCL_ERROR;
1170        }
1171    }
1172    if (!defer || push) {
1173        proxyPtr->flags |= UPDATE_PENDING;
1174    }
1175    if (push) {
1176        proxyPtr->flags |= FORCE_UPDATE;
1177    }
1178
1179    /* Does not invalidate cache? */
1180
1181    allocated = NULL;
1182    if (nBytes >= 0) {
1183        allocated = malloc(sizeof(char) * nBytes);
1184        if (allocated == NULL) {
1185            Tcl_AppendResult(interp, "can't allocate buffer for pdbdata.",
1186                             (char *)NULL);
1187            return TCL_ERROR;
1188        }
1189        if (GetBytes(&proxyPtr->client, allocated, nBytes) != BUFFER_OK) {
1190            Tcl_AppendResult(interp, "can't read pdbdata from client.",
1191                             (char *)NULL);
1192            free(allocated);
1193            return TCL_ERROR;
1194        }
1195        data = allocated;
1196    } else {
1197        nBytes = strlen(data);
1198    }
1199    {
1200        char fileName[200];
1201        FILE *f;
1202        ssize_t nWritten;
1203        static unsigned long count = 0;
1204
1205        proxyPtr->status = TCL_ERROR;
1206        sprintf(fileName, "/tmp/pymol%d/%ld.pdb", getpid(), count);
1207        count++;
1208        f = fopen(fileName, "w");
1209        if (f == NULL) {
1210            Tcl_AppendResult(interp, "can't create temporary file \"",
1211                             fileName, "\": ", Tcl_PosixError(interp),
1212                             (char *)NULL);
1213            goto error;
1214        }
1215        nWritten = fwrite(data, sizeof(char), nBytes, f);
1216        if (nBytes != nWritten) {
1217            Tcl_AppendResult(interp, "can't write PDB data to \"",
1218                             fileName, "\": ", Tcl_PosixError(interp),
1219                             (char *)NULL);
1220            fclose(f);
1221            goto error;
1222        }
1223        fclose(f);
1224        Pymol(proxyPtr, "load %s,%s,%d\n", fileName, name, state);
1225        proxyPtr->status = TCL_OK;
1226    }
1227 error:
1228    if (allocated != NULL) {
1229        free(allocated);
1230    }
1231    return proxyPtr->status;
1232}
1233
1234static int
1235OrthoscopicCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1236              const char *argv[])
1237{
1238    int bool, defer, push, i;
1239    PymolProxy *proxyPtr = clientData;
1240
1241    clear_error(proxyPtr);
1242    defer = push = FALSE;
1243    bool = FALSE;
1244    for(i = 1; i < argc; i++) {
1245        if (strcmp(argv[i],"-defer") == 0) {
1246            defer = TRUE;
1247        } else if (strcmp(argv[i],"-push") == 0) {
1248            push = TRUE;
1249        } else {
1250            if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
1251                return TCL_ERROR;
1252            }
1253        }
1254    }
1255    proxyPtr->flags |= INVALIDATE_CACHE; /* orthoscopic */
1256    if (!defer || push) {
1257        proxyPtr->flags |= UPDATE_PENDING;
1258    }
1259    if (push) {
1260        proxyPtr->flags |= FORCE_UPDATE;
1261    }
1262    Pymol(proxyPtr, "set orthoscopic=%d\n", bool);
1263    return proxyPtr->status;
1264}
1265
1266/*
1267 * PanCmd --
1268 *
1269 *      Issue "move" commands for changes in the x and y coordinates of the
1270 *      camera.  The problem here is that there is no event compression.
1271 *      Consecutive "pan" commands are not compressed into a single
1272 *      directive.  The means that the pymol server will render scenes that
1273 *      are not used by the client.
1274 *
1275 *      Need to 1) defer the "move" commands until we find the next command
1276 *      isn't a "pan". 2) Track the x and y coordinates as they are
1277 *      compressed.
1278 */
1279static int
1280PanCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1281        const char *argv[])
1282{
1283    PymolProxy *proxyPtr = clientData;
1284    double x, y;
1285    int i;
1286    int defer, push;
1287
1288    clear_error(proxyPtr);
1289    defer = push = FALSE;
1290    for (i = 1; i < argc; i++) {
1291        if (strcmp(argv[i],"-defer") == 0) {
1292            defer = 1;
1293        } else if (strcmp(argv[i],"-push") == 0) {
1294            push = 1;
1295        } else {
1296            break;
1297        }
1298    }
1299    if ((Tcl_GetDouble(interp, argv[i], &x) != TCL_OK) ||
1300        (Tcl_GetDouble(interp, argv[i+1], &y) != TCL_OK)) {
1301        return TCL_ERROR;
1302    }
1303    proxyPtr->flags |= INVALIDATE_CACHE; /* pan */
1304    if (!defer || push) {
1305        proxyPtr->flags |= UPDATE_PENDING;
1306    }
1307    if (push) {
1308        proxyPtr->flags |= FORCE_UPDATE;
1309    }
1310    if ((x != 0.0f) || (y != 0.0f)) {
1311        proxyPtr->xPan = x * 0.05;
1312        proxyPtr->yPan = -y * 0.05;
1313        proxyPtr->flags |= PAN_PENDING;
1314    }
1315    return proxyPtr->status;
1316}
1317
1318static int
1319PngCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1320{
1321    char buffer[800];
1322    int nBytes=0;
1323    PymolProxy *proxyPtr = clientData;
1324    size_t length;
1325    Image *imgPtr;
1326
1327    clear_error(proxyPtr);
1328
1329    /* Force pymol to update the current scene. */
1330    Pymol(proxyPtr, "refresh\n");
1331    Pymol(proxyPtr, "png -\n");
1332
1333    Expect(proxyPtr, "png image follows: ", buffer, 800);
1334
1335    if (sscanf(buffer, "png image follows: %d\n", &nBytes) != 1) {
1336        Tcl_AppendResult(interp, "can't get # bytes from \"", buffer, "\"",
1337                         (char *)NULL);
1338        return TCL_ERROR;
1339    }
1340    sprintf(buffer, "nv>image %d %d %d %d\n", nBytes, proxyPtr->cacheId,
1341            proxyPtr->frame, proxyPtr->rockOffset);
1342    length = strlen(buffer);
1343    imgPtr = NewImage(proxyPtr, nBytes + length);
1344    strcpy(imgPtr->data, buffer);
1345    if (GetBytes(&proxyPtr->server, imgPtr->data + length, nBytes)!=BUFFER_OK){
1346        ERROR("can't read %d bytes for \"image follows\" buffer", nBytes);
1347        return  TCL_ERROR;
1348    }
1349    Expect(proxyPtr, " ScenePNG", buffer, 800);
1350
1351    stats.nFrames++;
1352    stats.nBytes += nBytes;
1353    return proxyPtr->status;
1354}
1355
1356
1357static int
1358PrintCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1359         const char *argv[])
1360{
1361    char buffer[800];
1362    int nBytes=0;
1363    PymolProxy *proxyPtr = clientData;
1364    size_t length;
1365    Image *imgPtr;
1366    int width, height;
1367    const char *token, *bgcolor;
1368
1369    clear_error(proxyPtr);
1370
1371    if (argc != 5) {
1372        Tcl_AppendResult(interp, "wrong # arguments: should be \"",
1373                         argv[0], " token width height color\"", (char *)NULL);
1374        return TCL_ERROR;
1375    }
1376    token = argv[1];
1377    if (Tcl_GetInt(interp, argv[2], &width) != TCL_OK) {
1378        return TCL_ERROR;
1379    }
1380    if (Tcl_GetInt(interp, argv[3], &height) != TCL_OK) {
1381        return TCL_ERROR;
1382    }
1383    bgcolor = argv[4];
1384    /* Force pymol to update the current scene. */
1385    if (strcmp(bgcolor, "none") == 0) {
1386        Pymol(proxyPtr, "set ray_opaque_background,off\n");
1387        Pymol(proxyPtr, "refresh\n", bgcolor);
1388    } else {
1389        Pymol(proxyPtr, "set ray_opaque_background,on\n");
1390        Pymol(proxyPtr, "bg_color %s\nrefresh\n", bgcolor);
1391    }
1392    Pymol(proxyPtr, "ray %d,%d\n", width, height);
1393    Expect(proxyPtr, " Ray:", buffer, 800);
1394
1395    Pymol(proxyPtr, "png -,dpi=300\nbg_color black\n");
1396    Expect(proxyPtr, "png image follows: ", buffer, 800);
1397
1398    if (sscanf(buffer, "png image follows: %d\n", &nBytes) != 1) {
1399        Tcl_AppendResult(interp, "can't get # bytes from \"", buffer, "\"",
1400                         (char *)NULL);
1401        return TCL_ERROR;
1402    }
1403    sprintf(buffer, "nv>image %d print \"%s\" %d\n", nBytes, token,
1404            proxyPtr->rockOffset);
1405    Debug("header is png is (%s)\n", buffer);
1406    length = strlen(buffer);
1407    imgPtr = NewImage(proxyPtr, nBytes + length);
1408    strcpy(imgPtr->data, buffer);
1409    if (GetBytes(&proxyPtr->server, imgPtr->data + length, nBytes)!=BUFFER_OK){
1410        ERROR("can't read %d bytes for \"image follows\" buffer", nBytes);
1411        return  TCL_ERROR;
1412    }
1413    Expect(proxyPtr, " ScenePNG", buffer, 800);
1414
1415    stats.nFrames++;
1416    stats.nBytes += nBytes;
1417    return proxyPtr->status;
1418}
1419
1420static int
1421RawCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1422{
1423    PymolProxy *proxyPtr = clientData;
1424    int arg, defer = 0, push = 0;
1425    const char *cmd;
1426    clear_error(proxyPtr);
1427
1428    cmd = NULL;
1429    for(arg = 1; arg < argc; arg++) {
1430        if (strcmp(argv[arg], "-defer") == 0)
1431            defer = 1;
1432        else if (strcmp(argv[arg], "-push") == 0)
1433            push = 1;
1434        else {
1435            cmd = argv[arg];
1436        }
1437    }
1438
1439    proxyPtr->flags |= INVALIDATE_CACHE; /* raw */
1440    if (!defer || push) {
1441        proxyPtr->flags |= UPDATE_PENDING;
1442    }
1443    if (push) {
1444        proxyPtr->flags |= FORCE_UPDATE;
1445    }
1446    Pymol(proxyPtr,"%s\n", cmd);
1447    return proxyPtr->status;
1448}
1449
1450static int
1451ResetCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1452         const char *argv[])
1453{
1454    PymolProxy *proxyPtr = clientData;
1455    int arg, push = 0, defer = 0;
1456
1457    clear_error(proxyPtr);
1458
1459    for(arg = 1; arg < argc; arg++) {
1460        if ( strcmp(argv[arg],"-defer") == 0 )
1461            defer = 1;
1462        else if (strcmp(argv[arg],"-push") == 0 )
1463            push = 1;
1464    }
1465               
1466    proxyPtr->flags |= INVALIDATE_CACHE; /* reset */
1467    if (!defer || push) {
1468        proxyPtr->flags |= UPDATE_PENDING;
1469    }
1470    if (push) {
1471        proxyPtr->flags |= FORCE_UPDATE;
1472    }
1473    Pymol(proxyPtr,
1474          "reset\n"
1475          "zoom complete=1\n");
1476    return proxyPtr->status;
1477}
1478
1479static int
1480RockCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1481        const char *argv[])
1482{
1483    PymolProxy *proxyPtr = clientData;
1484    float y = 0.0;
1485    int arg, push = 0, defer = 0;
1486
1487    clear_error(proxyPtr);
1488
1489    for(arg = 1; arg < argc; arg++) {
1490        if ( strcmp(argv[arg],"-defer") == 0 )
1491            defer = 1;
1492        else if (strcmp(argv[arg],"-push") == 0 )
1493            push = 1;
1494        else
1495            y = atof( argv[arg] );
1496    }
1497               
1498    /* Does not invalidate cache. */
1499
1500    if (!defer || push) {
1501        proxyPtr->flags |= UPDATE_PENDING;
1502    }
1503    if (push) {
1504        proxyPtr->flags |= FORCE_UPDATE;
1505    }
1506    Pymol(proxyPtr,"turn y, %f\n", y - proxyPtr->rockOffset);
1507    proxyPtr->rockOffset = y;
1508    return proxyPtr->status;
1509}
1510
1511static int
1512RepresentationCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1513                  const char *argv[])
1514{
1515    PymolProxy *proxyPtr = clientData;
1516    const char *model;
1517    const char *rep;
1518    int defer, push, i;
1519
1520    clear_error(proxyPtr);
1521    defer = push = FALSE;
1522    model = "all";
1523    rep = NULL;
1524    for (i = 1; i < argc; i++) {
1525        if (strcmp(argv[i],"-defer") == 0 ) {
1526            defer = TRUE;
1527        } else if (strcmp(argv[i],"-push") == 0) {
1528            push = TRUE;
1529        } else if (strcmp(argv[i],"-model") == 0) {
1530            if (++i < argc) {
1531                model = argv[i];
1532            }
1533        } else {
1534            rep = argv[i];
1535        }
1536    }
1537    if (rep == NULL) {
1538        Tcl_AppendResult(interp, "missing representation argument",
1539                         (char *)NULL);
1540        return TCL_ERROR;
1541    }
1542
1543    proxyPtr->flags |= INVALIDATE_CACHE; /* representation */
1544    if (!defer || push) {
1545        proxyPtr->flags |= UPDATE_PENDING;
1546    }
1547    if (push) {
1548        proxyPtr->flags |= FORCE_UPDATE;
1549    }
1550    if (strcmp(rep, "ballnstick") == 0) { /* Ball 'n Stick */
1551        Pymol(proxyPtr,
1552              "set stick_color,white,%s\n"
1553              "show sticks,%s\n"
1554              "show spheres,%s\n"
1555              "hide lines,%s\n"
1556              "hide cartoon,%s\n",
1557              model, model, model, model, model);
1558    } else if (strcmp(rep, "spheres") == 0) { /* spheres */   
1559        Pymol(proxyPtr,
1560              "hide sticks,%s\n"
1561              "show spheres,%s\n"
1562              "hide lines,%s\n"
1563              "hide cartoon,%s\n"
1564              "set sphere_quality,2,%s\n"
1565              "set ambient,.2,%s\n",
1566              model, model, model, model, model, model);
1567    } else if (strcmp(rep, "none") == 0) { /* nothing */   
1568        Pymol(proxyPtr,
1569              "hide sticks,%s\n",
1570              "hide spheres,%s\n"
1571              "hide lines,%s\n"
1572              "hide cartoon,%s\n",
1573              model, model, model, model);
1574    } else if (strcmp(rep, "sticks") == 0) { /* sticks */   
1575        Pymol(proxyPtr,
1576              "set stick_color,white,%s\n"
1577              "show sticks,%s\n"
1578              "hide spheres,%s\n"
1579              "hide lines,%s\n"
1580              "hide cartoon,%s\n",
1581              model, model, model, model, model);
1582    } else if (strcmp(rep, "lines") == 0) { /* lines */   
1583        Pymol(proxyPtr,
1584              "hide sticks,%s\n"
1585              "hide spheres,%s\n"
1586              "show lines,%s\n"
1587              "hide cartoon,%s\n",
1588              model, model, model, model);
1589    } else if (strcmp(rep, "cartoon") == 0) { /* cartoon */   
1590        Pymol(proxyPtr,
1591              "hide sticks,%s\n"
1592              "hide spheres,%s\n"
1593              "hide lines,%s\n"
1594              "show cartoon,%s\n",
1595              model, model, model, model);
1596    }
1597    return proxyPtr->status;
1598}
1599
1600/*
1601 * RotateCmd --
1602 *
1603 *      Issue "turn" commands for changes in the angle of the camera.  The
1604 *      problem here is that there is no event compression.  Consecutive
1605 *      "rotate" commands are not compressed into a single directive.  The
1606 *      means that the pymol server will render many scene that are not used
1607 *      by the client.
1608 *
1609 *      Need to 1) defer the "turn" commands until we find the next command
1610 *      isn't a "rotate". 2) Track the rotation angles as they are compressed.
1611 */
1612static int
1613RotateCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1614          const char *argv[])
1615{
1616    float xAngle, yAngle, zAngle;
1617    PymolProxy *proxyPtr = clientData;
1618    int defer = 0, push = 0, arg, varg = 1;
1619
1620    clear_error(proxyPtr);
1621
1622    xAngle = yAngle = zAngle = 0.0f;
1623    for(arg = 1; arg < argc; arg++) {
1624        if (strcmp(argv[arg],"-defer") == 0) {
1625            defer = 1;
1626        } else if (strcmp(argv[arg],"-push") == 0) {
1627            push = 1;
1628        } else  if (varg == 1) {
1629            double value;
1630            if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
1631                return TCL_ERROR;
1632            }
1633            xAngle = (float)value;
1634            varg++;
1635        } else if (varg == 2) {
1636            double value;
1637            if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
1638                return TCL_ERROR;
1639            }
1640            yAngle = (float)value;
1641            varg++;
1642        } else if (varg == 3) {
1643            double value;
1644            if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
1645                return TCL_ERROR;
1646            }
1647            zAngle = (float)value;
1648            varg++;
1649        }
1650    }
1651    proxyPtr->flags |= INVALIDATE_CACHE; /* rotate */
1652    if (!defer || push) {
1653        proxyPtr->flags |= UPDATE_PENDING;
1654    }
1655    if (push) {
1656        proxyPtr->flags |= FORCE_UPDATE;
1657    }
1658    if ((xAngle != 0.0f) || (yAngle != 0.0f) || (zAngle != 0.0f)) {
1659        proxyPtr->xAngle += xAngle;
1660        proxyPtr->yAngle += yAngle;
1661        proxyPtr->zAngle += zAngle;
1662        proxyPtr->flags |= ROTATE_PENDING;
1663    }
1664    return proxyPtr->status;
1665}
1666
1667static int
1668ScreenCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1669          const char *argv[])
1670{
1671    PymolProxy *proxyPtr = clientData;
1672    int width = -1, height = -1;
1673    int defer, push, i, varg;
1674
1675    clear_error(proxyPtr);
1676    defer = push = FALSE;
1677    varg = 1;
1678    for(i = 1; i < argc; i++) {
1679        if ( strcmp(argv[i],"-defer") == 0 )
1680            defer = 1;
1681        else if ( strcmp(argv[i], "-push") == 0 )
1682            push = 1;
1683        else if (varg == 1) {
1684            width = atoi(argv[i]);
1685            height = width;
1686            varg++;
1687        }
1688        else if (varg == 2) {
1689            height = atoi(argv[i]);
1690            varg++;
1691        }
1692    }
1693    if ((width < 0) || (height < 0)) {
1694        return TCL_ERROR;
1695    }
1696    proxyPtr->flags |= INVALIDATE_CACHE; /* viewport */
1697    if (!defer || push) {
1698        proxyPtr->flags |= UPDATE_PENDING;
1699    }
1700    if (push) {
1701        proxyPtr->flags |= FORCE_UPDATE;
1702    }
1703    proxyPtr->width = width;
1704    proxyPtr->height = height;
1705    proxyPtr->flags |= VIEWPORT_PENDING;
1706
1707    //usleep(205000); // .2s delay for pymol to update its geometry *HACK ALERT*
1708       
1709    return proxyPtr->status;
1710}
1711
1712static int
1713SphereScaleCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1714           const char *argv[])
1715{
1716    int defer = 0, push = 0, i;
1717    double scale;
1718    const char *model = "all";
1719    PymolProxy *proxyPtr = clientData;
1720
1721    clear_error(proxyPtr);
1722    scale = 0.25f;
1723    for(i = 1; i < argc; i++) {
1724        if ( strcmp(argv[i],"-defer") == 0 ) {
1725            defer = 1;
1726        } else if (strcmp(argv[i],"-push") == 0) {
1727            push = 1;
1728        } else if (strcmp(argv[i],"-model") == 0) {
1729            if (++i < argc) {
1730                model = argv[i];
1731            }
1732        } else {
1733            if (Tcl_GetDouble(interp, argv[i], &scale) != TCL_OK) {
1734                return TCL_ERROR;
1735            }
1736        }
1737    }
1738    proxyPtr->flags |= INVALIDATE_CACHE;  /* sphere_scale */
1739    if (!defer || push) {
1740        proxyPtr->flags |= UPDATE_PENDING;
1741    }
1742    if (push) {
1743        proxyPtr->flags |= FORCE_UPDATE;
1744    }
1745
1746    if (strcmp(model, "all") == 0) {
1747        proxyPtr->flags |= ATOM_SCALE_PENDING;
1748        proxyPtr->sphereScale = scale;
1749    } else {
1750        Pymol(proxyPtr, "set sphere_scale,%f,%s\n", scale, model);
1751    }
1752    return proxyPtr->status;
1753}
1754
1755static int
1756StickRadiusCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1757                 const char *argv[])
1758{
1759    int defer = 0, push = 0, i;
1760    double scale;
1761    const char *model = "all";
1762    PymolProxy *proxyPtr = clientData;
1763
1764    clear_error(proxyPtr);
1765    scale = 0.25f;
1766    for(i = 1; i < argc; i++) {
1767        if ( strcmp(argv[i],"-defer") == 0 ) {
1768            defer = 1;
1769        } else if (strcmp(argv[i],"-push") == 0) {
1770            push = 1;
1771        } else if (strcmp(argv[i],"-model") == 0) {
1772            if (++i < argc)
1773                model = argv[i];
1774        } else {
1775            if (Tcl_GetDouble(interp, argv[i], &scale) != TCL_OK) {
1776                return TCL_ERROR;
1777            }
1778        }
1779    }
1780    proxyPtr->flags |= INVALIDATE_CACHE;  /* stick_radius */
1781    if (!defer || push) {
1782        proxyPtr->flags |= UPDATE_PENDING;
1783    }
1784    if (push) {
1785        proxyPtr->flags |= FORCE_UPDATE;
1786    }
1787
1788    if (strcmp(model, "all") == 0) {
1789        proxyPtr->flags |= STICK_RADIUS_PENDING;
1790        proxyPtr->stickRadius = scale;
1791    } else {
1792        Pymol(proxyPtr, "set stick_radius,%f,%s\n", scale, model);
1793    }
1794    return proxyPtr->status;
1795}
1796
1797static int
1798TransparencyCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1799                const char *argv[])
1800{
1801    PymolProxy *proxyPtr = clientData;
1802    const char *model;
1803    float transparency;
1804    int defer, push;
1805    int i;
1806
1807    clear_error(proxyPtr);
1808    model = "all";
1809    defer = push = FALSE;
1810    transparency = 0.0f;
1811    for(i = 1; i < argc; i++) {
1812        if ( strcmp(argv[i],"-defer") == 0 ) {
1813            defer = 1;
1814        } else if (strcmp(argv[i],"-push") == 0) {
1815            push = 1;
1816        } else if (strcmp(argv[i],"-model") == 0) {
1817            if (++i < argc) {
1818                model = argv[i];
1819            }
1820        } else {
1821            transparency = atof(argv[i]);
1822        }
1823    }
1824    proxyPtr->flags |= INVALIDATE_CACHE; /* transparency */
1825    if (!defer || push) {
1826        proxyPtr->flags |= UPDATE_PENDING;
1827    }
1828    if (push) {
1829        proxyPtr->flags |= FORCE_UPDATE;
1830    }
1831    Pymol(proxyPtr,
1832          "set sphere_transparency,%g,%s\n"
1833          "set stick_transparency,%g,%s\n"
1834          "set cartoon_transparency,%g,%s\n",
1835          transparency, model, transparency, model,
1836          transparency, model);
1837    return proxyPtr->status;
1838}
1839
1840static int
1841VMouseCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1842          const char *argv[])
1843{
1844    PymolProxy *proxyPtr = clientData;
1845    int i, defer = 0, push = 0, varg = 1;
1846    int arg1 = 0, arg2 = 0, arg3 = 0, arg4 = 0, arg5 = 0;
1847
1848    clear_error(proxyPtr);
1849
1850    for(i = 1; i < argc; i++) {
1851        if (strcmp(argv[i], "-defer") == 0)
1852            defer = 1;
1853        else if (strcmp(argv[i], "-push") == 0)
1854            push = 1;
1855        else if (varg == 1) {
1856            arg1 = atoi(argv[i]);
1857            varg++;
1858        }
1859        else if (varg == 2) {
1860            arg2 = atoi(argv[i]);
1861            varg++;
1862        }
1863        else if (varg == 3) {
1864            arg3 = atoi(argv[i]);
1865            varg++;
1866        }
1867        else if (varg == 4) {
1868            arg4 = atoi(argv[i]);
1869            varg++;
1870        }
1871        else if (varg == 5) {
1872            arg5 = atoi(argv[i]);
1873            varg++;
1874        }
1875    }
1876
1877    proxyPtr->flags |= INVALIDATE_CACHE; /* vmouse */
1878    if (!defer || push) {
1879        proxyPtr->flags |= UPDATE_PENDING;
1880    }
1881    if (push) {
1882        proxyPtr->flags |= FORCE_UPDATE;
1883    }
1884    Pymol(proxyPtr, "vmouse %d,%d,%d,%d,%d\n", arg1, arg2, arg3, arg4, arg5);
1885    return proxyPtr->status;
1886}
1887
1888
1889/*
1890 * ZoomCmd --
1891 *
1892 *      Issue "move" commands for changes in the z-coordinate of the camera.
1893 *      The problem here is that there is no event compression.  Consecutive
1894 *      "zoom" commands are not compressed into a single directive.  The means
1895 *      that the pymol server will render scenes that are not used by the
1896 *      client.
1897 *
1898 *      Need to 1) defer the "move" commands until we find the next command
1899 *      isn't a "zoom". 2) Track the z-coordinate as they are compressed.
1900 */
1901static int
1902ZoomCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1903{
1904    double factor = 0.0;
1905    PymolProxy *proxyPtr = clientData;
1906    int defer = 0, push = 0, i, varg = 1;
1907
1908    clear_error(proxyPtr);
1909
1910    for(i = 1; i < argc; i++) {
1911        if (strcmp(argv[i],"-defer") == 0)
1912            defer = 1;
1913        else if (strcmp(argv[i],"-push") == 0)
1914            push = 1;
1915        else if (varg == 1) {
1916            double value;
1917            if (Tcl_GetDouble(interp, argv[i], &value) != TCL_OK) {
1918                return TCL_ERROR;
1919            }
1920            factor = (float)value;
1921            varg++;
1922        }
1923    }
1924    proxyPtr->flags |= INVALIDATE_CACHE; /* Zoom */
1925    if (!defer || push) {
1926        proxyPtr->flags |= UPDATE_PENDING;
1927    }
1928    if (push) {
1929        proxyPtr->flags |= FORCE_UPDATE;
1930    }
1931    if (factor != 0.0) {
1932        proxyPtr->zoom = factor;
1933        proxyPtr->flags |= ZOOM_PENDING;
1934    }
1935    return proxyPtr->status;
1936}
1937
1938       
1939static int
1940ProxyInit(int cin, int cout, char *const *argv)
1941{
1942    int status, result = 0;
1943    int sin[2];
1944    int sout[2];
1945    int serr[2];
1946    Tcl_Interp *interp;
1947    int child;
1948    PymolProxy proxy;
1949    struct timeval end;
1950
1951    /* Create three pipes for communication with the external application. One
1952     * each for the applications's: stdin, stdout, and stderr  */
1953
1954    if (pipe(sin) == -1)
1955        return -1;
1956
1957    if (pipe(sout) == -1) {
1958        close(sin[0]);
1959        close(sin[1]);
1960        return -1;
1961    }
1962
1963    if (pipe(serr) == -1) {
1964        close(sin[0]);
1965        close(sin[1]);
1966        close(sout[0]);
1967        close(sout[1]);
1968        return -1;
1969    }
1970
1971    /* Fork the new process.  Connect i/o to the new socket.  */
1972
1973    child = fork();
1974    if (child < 0) {
1975        ERROR("can't fork process: %s\n", strerror(errno));
1976        return -3;
1977    }
1978    if (child == 0) {
1979        int f;
1980
1981        /* Child process */
1982       
1983        /*
1984         * Create a new process group, so we can later kill this process and
1985         * all its children without affecting the process that created this
1986         * one.
1987         */
1988        setpgid(child, 0);
1989       
1990        /* Redirect stdin, stdout, and stderr to pipes before execing */
1991
1992        dup2(sin[0], 0);  // stdin
1993        dup2(sout[1],1);  // stdout
1994        dup2(serr[1],2);  // stderr
1995       
1996        /* Close all other descriptors  */       
1997        for (f = 3; f < FD_SETSIZE; f++) {
1998            close(f);
1999        }
2000       
2001        execvp(argv[0], argv);
2002        ERROR("Failed to start pymol `%s'", argv[0]);
2003        exit(-1);
2004    }
2005    stats.child = child;
2006
2007    Debug("Started %s DISPLAY=%s\n", argv[0], getenv("DISPLAY"));
2008
2009    /* close opposite end of pipe, these now belong to the child process  */
2010    close(sin[0]);
2011    close(sout[1]);
2012    close(serr[1]);
2013
2014#ifdef notdef
2015    signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE (e.g. nanoscale terminates)*/
2016#endif
2017
2018    memset(&proxy, 0, sizeof(PymolProxy));
2019    proxy.sin  = sin[1];
2020    proxy.sout = sout[0];
2021    proxy.serr = serr[0];
2022    proxy.cin  = cin;
2023    proxy.cout = cout;
2024    proxy.flags = CAN_UPDATE;
2025    proxy.frame = 1;
2026    interp = Tcl_CreateInterp();
2027    if (CreateTmpDir(interp) != TCL_OK) {
2028        ERROR(Tcl_GetStringResult(interp));
2029    }
2030    Tcl_MakeSafe(interp);
2031    proxy.interp = interp;
2032
2033    Tcl_CreateCommand(interp, "bmp",           BmpCmd,           &proxy, NULL);
2034    Tcl_CreateCommand(interp, "cartoon",       CartoonCmd,       &proxy, NULL);
2035    Tcl_CreateCommand(interp, "cartoontrace",  CartoonTraceCmd,  &proxy, NULL);
2036    Tcl_CreateCommand(interp, "disable",       DisableCmd,       &proxy, NULL);
2037    Tcl_CreateCommand(interp, "enable",        EnableCmd,        &proxy, NULL);
2038    Tcl_CreateCommand(interp, "frame",         FrameCmd,         &proxy, NULL);
2039    Tcl_CreateCommand(interp, "label",         LabelCmd,         &proxy, NULL);
2040    Tcl_CreateCommand(interp, "loadpdb",       LoadPDBCmd,       &proxy, NULL);
2041    Tcl_CreateCommand(interp, "orthoscopic",   OrthoscopicCmd,   &proxy, NULL);
2042    Tcl_CreateCommand(interp, "pan",           PanCmd,           &proxy, NULL);
2043    Tcl_CreateCommand(interp, "png",           PngCmd,           &proxy, NULL);
2044    Tcl_CreateCommand(interp, "print",         PrintCmd,         &proxy, NULL);
2045    Tcl_CreateCommand(interp, "raw",           RawCmd,           &proxy, NULL);
2046    Tcl_CreateCommand(interp, "representation",RepresentationCmd,&proxy, NULL);
2047    Tcl_CreateCommand(interp, "reset",         ResetCmd,         &proxy, NULL);
2048    Tcl_CreateCommand(interp, "rock",          RockCmd,          &proxy, NULL);
2049    Tcl_CreateCommand(interp, "rotate",        RotateCmd,        &proxy, NULL);
2050    Tcl_CreateCommand(interp, "screen",        ScreenCmd,        &proxy, NULL);
2051    Tcl_CreateCommand(interp, "spherescale",   SphereScaleCmd,   &proxy, NULL);
2052    Tcl_CreateCommand(interp, "stickradius",   StickRadiusCmd,   &proxy, NULL);
2053    Tcl_CreateCommand(interp, "transparency",  TransparencyCmd,  &proxy, NULL);
2054    Tcl_CreateCommand(interp, "viewport",      ScreenCmd,        &proxy, NULL);
2055    Tcl_CreateCommand(interp, "vmouse",        VMouseCmd,        &proxy, NULL);
2056    Tcl_CreateCommand(interp, "zoom",          ZoomCmd,          &proxy, NULL);
2057
2058
2059    gettimeofday(&end, NULL);
2060    stats.start = end;
2061
2062    if (write(cout, "PyMol 1.0\n", 10) != 10) {
2063        ERROR("short write of signature");
2064    }
2065   
2066    // Main Proxy Loop
2067    //  accept tcl commands from socket
2068    //  translate them into pyMol commands, and issue them to child proccess
2069    //  send images back
2070    PollForEvents(&proxy);
2071
2072    close(proxy.cout);
2073    close(proxy.sout);
2074    close(proxy.serr);
2075    close(proxy.cin);
2076    close(proxy.sin);
2077
2078    if (waitpid(child, &result, 0) < 0) {
2079        ERROR("error waiting on pymol server to exit: %s", strerror(errno));
2080    }
2081    INFO("attempting to signal (SIGTERM) pymol server.");
2082    kill(-child, SIGTERM);              // Kill process group
2083    alarm(5);
2084   
2085    if (waitpid(child, &result, 0) < 0) {
2086        ERROR("error waiting on pymol server to exit after SIGTERM: %s",
2087              strerror(errno));
2088    }
2089    status = -1;
2090    while ((status == -1) && (errno == EINTR)) {
2091        ERROR("Attempting to signal (SIGKILL) pymol server.");
2092        kill(-child, SIGKILL);  // Kill process group
2093        alarm(10);
2094        status = waitpid(child, &result, 0);
2095        alarm(0);
2096    }
2097    INFO("pymol server process ended (result=%d)", result);
2098
2099    DestroyTmpDir();
2100    Tcl_DeleteInterp(interp);
2101   
2102    if (WIFEXITED(result)) {
2103        result = WEXITSTATUS(result);
2104    }
2105    DoExit(result);
2106    return 0;
2107}
2108
2109
2110static void
2111PollForEvents(PymolProxy *proxyPtr)
2112{
2113    Tcl_DString clientCmds;
2114    struct pollfd pollResults[4];
2115    int flags;
2116
2117    flags = fcntl(proxyPtr->cin, F_GETFL);
2118    fcntl(proxyPtr->cin, F_SETFL, flags|O_NONBLOCK);
2119
2120    /* Read file descriptors. */
2121    pollResults[0].fd = proxyPtr->cout; /* Client standard output  */
2122    pollResults[1].fd = proxyPtr->sout; /* Server standard error.  */
2123    pollResults[2].fd = proxyPtr->serr; /* Server standard error.  */
2124    pollResults[0].events = pollResults[1].events =
2125        pollResults[2].events = POLLIN;
2126
2127    /* Write file descriptors. */
2128    pollResults[3].fd = proxyPtr->cin;  /* Client standard input. */
2129    pollResults[3].events = POLLOUT;
2130
2131    InitBuffer(&proxyPtr->client, proxyPtr->cout);
2132    InitBuffer(&proxyPtr->server, proxyPtr->sout);
2133
2134    Tcl_DStringInit(&clientCmds);
2135    for (;;) {
2136        int timeout, nChannels;
2137
2138        nChannels =  (proxyPtr->headPtr != NULL) ? 4 : 3;
2139
2140#define PENDING_TIMEOUT         10  /* milliseconds. */
2141        timeout = (proxyPtr->flags & UPDATE_PENDING) ? PENDING_TIMEOUT : -1;
2142        nChannels = poll(pollResults, nChannels, timeout);
2143        if (nChannels < 0) {
2144            ERROR("POLL ERROR: %s", strerror(errno));
2145            continue;           /* or exit? */
2146        }
2147
2148        /*
2149         * The next two sections are to drain any leftover data in
2150         * the pymol server process' stdout or stderr.  We don't want the
2151         * the pymol server to block writing to stderr or stdout.
2152         */
2153        if (pollResults[1].revents & POLLIN) {
2154            int nBytes;
2155            char *line;
2156           
2157            Debug("Reading pymol stdout\n");
2158            /* Don't care what's in the server output buffer. */
2159            FlushBuffer(&proxyPtr->server);
2160            line = GetLine(&proxyPtr->server, &nBytes);
2161            if (line != NULL) {
2162                Debug("STDOUT>%.*s", nBytes, line);
2163                Debug("Done with pymol stdout\n");
2164            } else if (nBytes == BUFFER_CONTINUE) {
2165                Debug("Done with pymol stdout\n");
2166            } else {
2167                ERROR("Failed reading pymol stdout (nBytes=%d)\n", nBytes);
2168                goto error;             /* Get out on EOF or error. */
2169            }
2170        }
2171
2172        if (pollResults[2].revents & POLLIN) {
2173            ssize_t nRead;
2174            char buf[BUFSIZ];
2175           
2176            Debug("Reading pymol stderr\n");
2177            /* pyMol Stderr Connection: pymol standard error output */
2178           
2179            nRead = read(pollResults[2].fd, buf, BUFSIZ-1);
2180            if (nRead <= 0) {
2181                ERROR("unexpected read error from server (stderr): %s",
2182                      strerror(errno));
2183                if (errno != EINTR) {
2184                    ERROR("lost connection (stderr) to pymol server.");
2185                    goto error;
2186                }
2187            }
2188            buf[nRead] = '\0';
2189            Debug("stderr>%s", buf);
2190            Debug("Done reading pymol stderr\n");
2191        }
2192
2193        /* We have some descriptors ready. */
2194        if (pollResults[0].revents & POLLIN) {
2195            Debug("Reading client stdout\n");
2196            for (;;) {
2197                int nBytes;
2198                char *line;
2199               
2200                line = GetLine(&proxyPtr->client, &nBytes);
2201                if (line != NULL) {
2202                    const char *cmd;
2203
2204                    Tcl_DStringAppend(&clientCmds, line, nBytes);
2205                    cmd = Tcl_DStringValue(&clientCmds);
2206                    if (Tcl_CommandComplete(cmd)) {
2207                        /* May execute more than one command. */
2208                        ExecuteCommand(proxyPtr->interp, cmd);
2209                        Tcl_DStringSetLength(&clientCmds, 0);
2210                    }
2211                    continue;
2212                }
2213                if (nBytes == BUFFER_CONTINUE) {
2214                    break;
2215                }
2216                ERROR("Failed reading client stdout (nBytes=%d)\n", nBytes);
2217                goto error;             /* Get out on EOF or error. */
2218            }
2219            Debug("done with client stdout\n");
2220        }
2221        /*
2222         * Write the currently queued image if there is one.
2223         *
2224         * We want to transmit the current image back to the client.  But if
2225         * the client's busy (e.g. sending us another command), there's a
2226         * chance we'll deadlock.  Therefore, the file descriptor is
2227         * non-blocking and we write only what we can.  Must be careful not to
2228         * refresh the image until we're done.
2229         */
2230
2231        /* Handle all the pending setting changes now. */
2232        UpdateSettings(proxyPtr);
2233
2234        /* Write the current image buffer. */
2235        if (proxyPtr->headPtr == NULL) {
2236            /* We might want to refresh the image if we're not currently
2237             * transmitting an image back to the client. The image will be
2238             * refreshed after the image has been completely transmitted. */
2239            if ((nChannels == 0) || (proxyPtr->flags & FORCE_UPDATE)) {
2240                if (proxyPtr->flags & UPDATE_PENDING) {
2241                    Tcl_Eval(proxyPtr->interp, "bmp");
2242                    proxyPtr->flags &= ~UPDATE_PENDING;
2243                }
2244                proxyPtr->flags &= ~FORCE_UPDATE;
2245                continue;
2246            }
2247        }
2248        if ((proxyPtr->headPtr != NULL) &&
2249            (pollResults[3].revents & POLLOUT)) {
2250            WriteImage(proxyPtr, pollResults[3].fd);
2251        }
2252    }
2253 error:
2254    Tcl_DStringFree(&clientCmds);
2255    return;
2256}
2257
2258int
2259main(int argc, char *argv[])
2260{
2261    flog = stderr;
2262    if (debug) {
2263        char fileName[200];
2264        sprintf(fileName, "/tmp/pymolproxy%d.log", getpid());
2265        sprintf(fileName, "/tmp/pymolproxy.log");
2266        flog = fopen(fileName, "w");
2267    }   
2268    scriptFile = NULL;
2269    if (savescript) {
2270        char fileName[200];
2271        sprintf(fileName, "/tmp/pymolproxy.py");
2272        scriptFile = fopen(fileName, "w");
2273    }   
2274    ProxyInit(fileno(stdout), fileno(stdin), argv + 1);
2275    return 0;
2276}
2277
Note: See TracBrowser for help on using the repository browser.