source: trunk/packages/vizservers/vmd/vmdserver.tcl @ 4104

Last change on this file since 4104 was 4104, checked in by gah, 7 years ago

Change render server identifier

File size: 26.3 KB
Line 
1# ----------------------------------------------------------------------
2#  HUBZERO: server for VMD
3#
4#  This program runs VMD and acts as a server for client applications.
5# ----------------------------------------------------------------------
6#  Michael McLennan (mmclennan@purdue.edu)
7# ======================================================================
8#  Copyright (c) 2013 - HUBzero Foundation, LLC
9# ======================================================================
10
11# The VMD TCL interpreter is by default interactive.  Turn this off
12# so that unknown commands like "scene" don't get exec-ed.
13set ::tcl_interactive 0
14
15proc bgerror {mesg} {
16    puts stderr "SERVER ERROR: $mesg"
17}
18
19# parse command line args
20set Paradigm "socket"
21while {[llength $argv] > 0} {
22    set opt [lindex $argv 0]
23    set argv [lrange $argv 1 end]
24
25    switch -- $opt {
26        -socket { set Paradigm "socket" }
27        -stdio  { set Paradigm "stdio" }
28        default {
29            puts stderr "bad option \"$opt\": should be -socket or -stdio"
30        }
31    }
32}
33
34# use this to take snapshots to send to clients
35image create photo SnapShot
36
37# set the screen to a good size
38set DisplaySize(w) 300
39set DisplaySize(h) 300
40display resize $DisplaySize(w) $DisplaySize(h)
41set DisplaySize(changed) 0
42
43# initialize work queue and epoch counter (see server_send_image)
44set Epoch 0
45set Work(queue) ""
46set Sendqueue ""
47set Scenes(@CURRENT) ""
48
49set parser [interp create -safe]
50
51foreach cmd {
52  vmdinfo
53  vmdbench
54  color
55  axes
56  display
57  imd
58  vmdcollab
59  vmd_label
60  light
61  material
62  vmd_menu
63  stage
64  light
65  user
66  mol
67  molinfo
68  molecule
69  mouse
70  mobile
71  spaceball
72  plugin
73  render
74  tkrender
75  rotate
76  rotmat
77  vmd_scale
78  translate
79  sleep
80  tool
81  measure
82  rawtimestep
83  gettimestep
84  vmdcon
85  volmap
86  parallel
87} {
88    $parser alias $cmd $cmd
89}
90
91# ----------------------------------------------------------------------
92# USAGE: tellme "command template with %v" command arg arg...
93#
94# Executes the "command arg arg..." string in the server and substitutes
95# the result into the template string in place of each "%v" field.
96# Sends the result back to the client.
97# ----------------------------------------------------------------------
98proc cmd_tellme {fmt args} {
99    global parser client
100
101    # evaluate args as a command and subst the result in the fmt string
102    if {[catch {$parser eval $args} result] == 0} {
103        server_send_result $client "nv>[string map [list %v $result] $fmt]"
104    } else {
105        server_oops $client $result
106    }
107}
108$parser alias tellme cmd_tellme
109
110# ----------------------------------------------------------------------
111# USAGE: resize <w> <h>
112#
113# Resizes the visualization window to the given width <w> and height
114# <h>.  The next image sent should be this size.
115# ----------------------------------------------------------------------
116proc cmd_resize {w h} {
117    global DisplayProps
118
119    # store the desired size in case we downscale
120    set DisplayProps(framew) $w
121    set DisplayProps(frameh) $h
122
123    server_safe_resize $w $h
124}
125$parser alias resize cmd_resize
126
127# ----------------------------------------------------------------------
128# USAGE: setview ?-rotate <mtx>? ?-scale <mtx>? ?-center <mtx>? ?-global <mtx>?
129#
130# Sets the view matrix for one or more components of the view.  This
131# is a convenient way of getting a view for a particular frame just
132# right in one shot.
133# ----------------------------------------------------------------------
134proc cmd_setview {args} {
135    if {[llength $args] == 8} {
136        # setting all matrices? then start clean
137        display resetview
138    }
139    foreach {key val} $args {
140        switch -- $key {
141            -rotate {
142                molinfo top set rotate_matrix [list $val]
143            }
144            -scale {
145                molinfo top set scale_matrix [list $val]
146            }
147            -center {
148                molinfo top set center_matrix [list $val]
149            }
150            -global {
151                molinfo top set global_matrix [list $val]
152            }
153            default {
154                error "bad option \"$key\": should be -rotate, -scale, -center, or -global"
155            }
156        }
157    }
158}
159$parser alias setview cmd_setview
160
161# ----------------------------------------------------------------------
162# USAGE: drag start|end
163#
164# Resizes the visualization window to the given width <w> and height
165# <h>.  The next image sent should be this size.
166# ----------------------------------------------------------------------
167proc cmd_drag {action} {
168    global DisplayProps
169
170    switch -- $action {
171        start {
172            # simplify rendering so it goes faster during drag operations
173            set neww [expr {round($DisplayProps(framew)/2.0)}]
174            set newh [expr {round($DisplayProps(frameh)/2.0)}]
175            server_safe_resize $neww $newh
176            display rendermode Normal
177            display shadows off
178
179            foreach nmol [molinfo list] {
180                set max [molinfo $nmol get numreps]
181                for {set nrep 0} {$nrep < $max} {incr nrep} {
182                    mol modstyle $nrep $nmol "Lines"
183                }
184            }
185        }
186        end {
187            # put original rendering options back
188            server_safe_resize $DisplayProps(framew) $DisplayProps(frameh)
189            display rendermode $DisplayProps(rendermode)
190            display shadows $DisplayProps(shadows)
191
192            # restore rendering methods for all representations
193            foreach nmol [molinfo list] {
194                set max [molinfo $nmol get numreps]
195                for {set nrep 0} {$nrep < $max} {incr nrep} {
196                    mol modstyle $nrep $nmol $DisplayProps(rep-$nmol-$nrep)
197                }
198            }
199        }
200        default {
201            error "bad option \"$action\": should be start or end"
202        }
203    }
204}
205$parser alias drag cmd_drag
206
207# ----------------------------------------------------------------------
208# USAGE: smoothreps <value>
209#
210# Changes the smoothing factor for all representations of the current
211# molecule.
212# ----------------------------------------------------------------------
213proc cmd_smoothreps {val} {
214    if {$val < 0} {
215        error "bad smoothing value \"$val\": should be >= 0"
216    }
217    foreach nmol [molinfo list] {
218        set max [molinfo $nmol get numreps]
219        for {set nrep 0} {$nrep < $max} {incr nrep} {
220            mol smoothrep $nmol $nrep $val
221        }
222    }
223}
224$parser alias smoothreps cmd_smoothreps
225
226# ----------------------------------------------------------------------
227# USAGE: animate <option> <args>...
228# USAGE: rock off
229# USAGE: rock x|y|z by <step> ?<n>?
230#
231# The usual VMD "animate" and "rock" commands are problematic for this
232# server.  If we're going to rock or play the animation, the client
233# will do it.  Intercept any "animate" and "rock" commands in the scene
234# scripts and do nothing.
235# ----------------------------------------------------------------------
236proc cmd_animate {args} {
237    # do nothing
238}
239$parser alias animate cmd_animate
240
241proc cmd_rock {args} {
242    # do nothing
243}
244$parser alias rock cmd_rock
245
246# ----------------------------------------------------------------------
247# USAGE: load <file> <file>...
248#
249# Loads the molecule data from one or more files, which may be PDB,
250# DCD, PSF, etc.
251# ----------------------------------------------------------------------
252proc cmd_load {args} {
253    # clear all existing molecules
254    foreach nmol [molinfo list] {
255        mol delete $nmol
256    }
257
258    # load new files
259    set op "new"
260    foreach file $args {
261        mol $op $file waitfor all
262        set op "addfile"
263    }
264}
265$parser alias load cmd_load
266
267# ----------------------------------------------------------------------
268# USAGE: scene define <name> <script>
269# USAGE: scene show <name> ?-send <initialViewCmd>?
270# USAGE: scene clear
271# USAGE: scene forget ?<name> <name>...?
272#
273# Used to define and manipulate scenes of the trajectory information
274# loaded previously by the "load" command.  The "define" operation
275# defines the script that loads a scene called <name>.  The "show"
276# operation executes that script to show the scene.  The "clear"
277# operation clears the current scene (usually in preparation for
278# showing another scene).  The "forget" operation erases one or more
279# scene definitions; if no names are specified, then all scenes are
280# forgotten.
281# ----------------------------------------------------------------------
282proc cmd_scene {option args} {
283    global Scenes Views DisplayProps parser
284
285    switch -- $option {
286        define {
287            if {[llength $args] != 2} {
288                error "wrong # args: should be \"scene define name script\""
289            }
290            set name [lindex $args 0]
291            set script [lindex $args 1]
292            set Scenes($name) $script
293        }
294        show {
295            if {[llength $args] < 1 || [llength $args] > 3} {
296                error "wrong # args: should be \"scene show name ?-send cmd?\""
297            }
298            set name [lindex $args 0]
299            if {![info exists Scenes($name)]} {
300                error "bad scene name \"$name\": should be one of [join [array names Scenes] {, }]"
301            }
302
303            set sendcmd ""
304            foreach {key val} [lrange $args 1 end] {
305                switch -- $key {
306                    -send { set sendcmd $val }
307                    default { error "bad option \"$key\": should be -send" }
308                }
309            }
310
311            # clear the old scene
312            cmd_scene clear
313
314            # use a safe interp to keep things safe
315            display resetview
316            if {[catch {$parser eval $Scenes($name)} result]} {
317                error "$result\nwhile loading scene \"$name\""
318            }
319
320            # capture display characteristics in case we ever need to reset
321            set DisplayProps(rendermode) [display get rendermode]
322            set DisplayProps(shadows) [display get shadows]
323
324            foreach nmol [molinfo list] {
325                set max [molinfo $nmol get numreps]
326                for {set nrep 0} {$nrep < $max} {incr nrep} {
327                    set style [lindex [molinfo $nmol get "{rep $nrep}"] 0]
328                    set DisplayProps(rep-$nmol-$nrep) $style
329                }
330            }
331
332            # store the scene name for later
333            set Scenes(@CURRENT) $name
334
335            # if -send arg was given, send back the view after the script
336            if {$sendcmd ne ""} {
337                cmd_tellme $sendcmd getview
338            }
339        }
340        clear {
341            foreach mol [molinfo list] {
342                set numOfRep [lindex [mol list $mol] 12]
343                for {set i 1} {$i <= $numOfRep} {incr i} {
344                    mol delrep 0 $mol
345                }
346            }
347            set Scenes(@CURRENT) ""
348            catch {unset Views}
349
350            # reset the server properties
351            axes location off
352            color Display Background black
353            display backgroundgradient off
354        }
355        forget {
356            if {[llength $args] == 0} {
357                set args [array names Scenes]
358            }
359            foreach name $args {
360                if {$name eq "@CURRENT"} continue
361                catch {unset Scenes($name)}
362                if {$name eq $Scenes(@CURRENT)} {
363                    set Scenes(@CURRENT) ""
364                }
365            }
366        }
367        default {
368            error "bad option \"$option\": should be define, show, clear, forget"
369        }
370    }
371}
372$parser alias scene cmd_scene
373
374# ----------------------------------------------------------------------
375# USAGE: frames defview <frame> {matrixNames...} {matrixValues...}
376# USAGE: frames time <epochValue> <start> ?<finish>? ?<inc>? ?-defview?
377# USAGE: frames rotate <epochValue> <xa> <ya> <za> <number>
378# USAGE: frames max
379#
380# Used to request one or more frames for an animation.  A "time"
381# animation is a series of frames between two time points.  A "rotate"
382# animation is a series of frames that rotate the view 360 degrees.
383#
384# The <epochValue> is passed by the client to indicate the relevance of
385# the request.  Whenever the client enters a new epoch, it is no longer
386# concerned with any earlier epochs, so the server can ignore pending
387# images that are out of date.  The server sends back the epoch with
388# all frames so the client can understand if the frames are relevant.
389#
390# The "defview" operation sets the default view associated with each
391# frame.  Animation scripts can change the default view to a series of
392# fly-through views.  This operation provides a way of storing those
393# views.
394#
395# For a "time" animation, the <start> is a number of a requested frame.
396# The <finish> is the last frame in the series.  The <inc> is the step
397# by which the frames should be generated, which may be larger than 1.
398#
399# For a "rotate" animation, the <xa>,<ya>,<za> angles indicate the
400# direction of the rotation.  The <number> is the number of frames
401# requested for a full 360 degree rotation.
402#
403# The "frames max" query returns the maximum number of frames in the
404# trajectory.  The server uses this to figure out the limits of
405# animation.
406# ----------------------------------------------------------------------
407proc cmd_frames {what args} {
408    global client Epoch Work Views
409
410    # check incoming parameters
411    switch -- $what {
412      time {
413        set epochValue [lindex $args 0]
414        set start [lindex $args 1]
415
416        set i [lsearch $args -defview]
417        if {$i >= 0} {
418            set defview 1
419            set args [lreplace $args $i $i]
420        } else {
421            set defview 0
422        }
423
424        set finish [lindex $args 2]
425        if {$finish eq ""} { set finish $start }
426        set inc [lindex $args 3]
427        if {$inc eq ""} { set inc 1 }
428
429        if {![string is integer $finish]} {
430            server_oops $client "bad animation end \"$finish\" should be integer"
431            return
432        }
433        if {![string is integer $inc] || $inc == 0} {
434            server_oops $client "bad animation inc \"$inc\" should be non-zero integer"
435            return
436        }
437        if {($finish < $start && $inc > 0) || ($finish > $start && $inc < 0)} {
438            server_oops $client "bad animation limits: from $start to $finish by $inc"
439        }
440
441        # new epoch? then clean out work queue
442        if {$epochValue > $Epoch} {
443            catch {unset Work}
444            set Work(queue) ""
445            set Epoch $epochValue
446        }
447
448        # add these frames to the queue
449        if {$inc > 0} {
450            # generate frames in play>> direction
451            for {set n $start} {$n <= $finish} {incr n $inc} {
452                if {![info exists Work($n)]} {
453                    lappend Work(queue) [list epoch $epochValue frame $n num $n defview $defview]
454                    set Work($n) 1
455                }
456            }
457        } else {
458            # generate frames in <<play direction
459            for {set n $start} {$n >= $finish} {incr n $inc} {
460                if {![info exists Work($n)]} {
461                    lappend Work(queue) [list epoch $epochValue frame $n num $n defview $defview]
462                    set Work($n) 1
463                }
464            }
465        }
466      }
467      rotate {
468        set epochValue [lindex $args 0]
469        set mx [lindex $args 1]
470        if {![string is double -strict $mx]} {
471            server_oops $client "bad mx rotation value \"$mx\" should be double"
472            return
473        }
474        set my [lindex $args 2]
475        if {![string is double -strict $my]} {
476            server_oops $client "bad my rotation value \"$my\" should be double"
477            return
478        }
479        set mz [lindex $args 3]
480        if {![string is double -strict $mz]} {
481            server_oops $client "bad mz rotation value \"$mz\" should be double"
482            return
483        }
484        set num [lindex $args 4]
485        if {![string is integer -strict $num] || $num < 2} {
486            server_oops $client "bad number of rotation frames \"$num\" should be integer > 1"
487            return
488        }
489
490        #
491        # Compute the rotation matrix for each rotated view.
492        # Start with the current rotation matrix.  Rotate that around
493        # a vector perpendicular to the plane of rotation for the given
494        # angles (mx,my,mz).  Find vector that by rotating some vector
495        # such as (1,1,1) by the angles (mx,my,mz).  Do a couple of
496        # times and compute the differences between those vectors.
497        # Then, compute the cross product of the differences.  The
498        # result is the axis of rotation.
499        #
500        set lastrotx [trans axis x $mx deg]
501        set lastroty [trans axis y $my deg]
502        set lastrotz [trans axis z $mz deg]
503        set lastrot [transmult $lastrotx $lastroty $lastrotz]
504
505        set lastvec [list 1 1 1]
506        foreach v {1 2} {
507            foreach row $lastrot comp {x y z w} {
508                # multiply each row by last vector
509                set vec($comp) 0
510                for {set i 0} {$i < 3} {incr i} {
511                    set vec($comp) [expr {$vec($comp) + [lindex $row $i]}]
512                }
513            }
514            set vec${v}(x) [expr {$vec(x)-[lindex $lastvec 0]}]
515            set vec${v}(y) [expr {$vec(y)-[lindex $lastvec 1]}]
516            set vec${v}(z) [expr {$vec(z)-[lindex $lastvec 2]}]
517
518            set lastvec [list $vec(x) $vec(y) $vec(z)]
519            set lastrot [transmult $lastrot $lastrotx $lastroty $lastrotz]
520        }
521
522        set crx [expr {$vec1(y)*$vec2(z)-$vec1(z)*$vec2(y)}]
523        set cry [expr {$vec1(z)*$vec2(x)-$vec1(x)*$vec2(z)}]
524        set crz [expr {$vec1(x)*$vec2(y)-$vec1(y)*$vec2(x)}]
525
526        set angle [expr {360.0/$num}]
527        set rotby [transabout [list $crx $cry $crz] $angle deg]
528        set rotm [lindex [molinfo top get rotate_matrix] 0]
529
530        # compute cross product of (1,1,1,0) and rotated vector from above
531
532        for {set n 0} {$n < $num} {incr n} {
533            lappend Work(queue) [list epoch $epochValue rotate $rotm num $n defview 0]
534            set rotm [transmult $rotby $rotm]
535            set Work($n) 1
536        }
537      }
538      defview {
539          if {[llength $args] != 3} { error "wrong # args: should be \"defview matrixNameList matrixValueList\"" }
540          set n [lindex $args 0]
541          if {![string is int $n]} { error "bad frame value \"$n\"" }
542          set Views($n) [lrange $args 1 end]
543      }
544      max {
545        set nmol [lindex [molinfo list] 0]
546        if {$nmol ne ""} {
547            return [molinfo $nmol get numframes]
548        }
549        return 0
550      }
551      default {
552        error "bad option \"$what\": should be defview, time, rotate, max"
553      }
554    }
555
556    # service the queue at some point
557    server_send_image -eventually
558}
559$parser alias frames cmd_frames
560
561# ----------------------------------------------------------------------
562# USAGE: getview
563#
564# Used to query the scaling and centering of the initial view set
565# by VMD after a molecule is loaded.  Returns the following:
566#   <viewName> -rotate <mtx> -global <mtx> -scale <mtx> -center <mtx>
567# ----------------------------------------------------------------------
568proc cmd_getview {} {
569    global Scenes
570
571    if {[llength [molinfo list]] == 0} { return "" }
572    if {$Scenes(@CURRENT) eq ""} { return "" }
573
574    set rval [list $Scenes(@CURRENT)]  ;# start with the scene name
575
576    lappend rval -rotate [lindex [molinfo top get rotate_matrix] 0] \
577                 -scale [lindex [molinfo top get scale_matrix] 0] \
578                 -center [lindex [molinfo top get center_matrix] 0] \
579                 -global [lindex [molinfo top get global_matrix] 0]
580
581    return $rval
582}
583$parser alias getview cmd_getview
584
585#
586# USAGE: server_safe_resize <width> <height>
587#
588# Use this version instead of "display resize" whenever possible.
589# The VMD "display resize" goes into the event loop, so calling that
590# causes things to execute out of order.  Use this method instead to
591# store the change and actually resize later.
592#
593proc server_safe_resize {w h} {
594    global DisplaySize
595
596    if {$w != $DisplaySize(w) || $h != $DisplaySize(h)} {
597        set DisplaySize(w) $w
598        set DisplaySize(h) $h
599        set DisplaySize(changed) yes
600    }
601}
602
603# ----------------------------------------------------------------------
604# SERVER CORE
605# ----------------------------------------------------------------------
606proc server_accept {cid addr port} {
607    fileevent $cid readable [list server_handle $cid $cid]
608    fconfigure $cid -buffering none -blocking 0
609
610    # identify server type to this client
611    # The server identifier must be in the form <name> <version>.  The
612    # base connect method will ignore characters until it finds this line.
613    puts $cid "vmd 0.1"
614}
615
616proc server_handle {cin cout} {
617    global parser buffer client
618
619    if {[gets $cin line] < 0} {
620        # when client drops connection, we can exit
621        # nanoscale will spawn a new server next time we need it
622        server_exit $cin $cout
623    } else {
624        append buffer($cin) $line "\n"
625        if {[info complete $buffer($cin)]} {
626            #puts stdout "command is ($buffer($cin))"
627            set request $buffer($cin)
628            set buffer($cin) ""
629            set client $cout
630            if {[catch {$parser eval $request} result] == 0} {
631                server_send_image -eventually
632            } else {
633                puts stdout "last gets is ($line) cmd=($request) result=($result)"
634                server_oops $cout $result
635                if { [string match "invalid command*" $result] } {
636                    bgerror $result
637                    exit 1
638                }
639            }
640        }
641    }
642}
643
644proc server_send {cout} {
645    global Epoch Sendqueue
646
647    # grab the next chunk of output and send it along
648    # discard any chunks from an older epoch
649    while {[llength $Sendqueue] > 0} {
650        set chunk [lindex $Sendqueue 0]
651        set Sendqueue [lrange $Sendqueue 1 end]
652
653        catch {unset data}; array set data $chunk
654        if {$data(epoch) == $Epoch} {
655            catch {puts $cout $data(cmd)}
656
657            # if this command has a binary data block, send it specially
658            if {[string length $data(bytes)] > 0} {
659                fconfigure $cout -translation binary
660                catch {puts $cout $data(bytes)}
661                fconfigure $cout -translation auto
662            }
663            break
664        }
665    }
666
667    # nothing left? Then stop callbacks until we get more
668    if {[llength $Sendqueue] == 0} {
669        fileevent $cout writable ""
670        server_send_image -eventually
671    }
672}
673
674proc server_exit {cin cout} {
675    catch {close $cin}
676    catch {exit 0}
677}
678
679# ----------------------------------------------------------------------
680# SERVER RESPONSES
681# ----------------------------------------------------------------------
682
683# turn off constant updates -- only need them during server_send_image
684display update off
685
686proc server_send_image {{when -now}} {
687    global client Epoch Work Views Sendqueue DisplaySize
688
689    if {$when eq "-eventually"} {
690        after cancel server_send_image
691        after 1 server_send_image
692        return
693    } elseif {$when ne "-now"} {
694        error "bad option \"$when\" for server_send_image: should be -now or -eventually"
695    }
696
697    # is there a display resize pending? then resize and try again later
698    if {$DisplaySize(changed)} {
699        set DisplaySize(changed) 0
700        after idle [list display resize $DisplaySize(w) $DisplaySize(h)]
701        after 20 server_send_image
702        return
703    }
704
705    # loop through requests in the work queue and skip any from an older epoch
706    while {1} {
707        if {[llength $Work(queue)] == 0} {
708            return
709        }
710
711        set rec [lindex $Work(queue) 0]
712        set Work(queue) [lrange $Work(queue) 1 end]
713
714        catch {unset item}; array set item $rec
715        if {$item(epoch) < $Epoch} {
716            catch {unset Work($item(num))}
717            continue
718        }
719
720        # set the frame characteristics and render this frame
721        if {[info exists item(frame)]} {
722            animate goto $item(frame)
723        } elseif {[info exists item(rotate)]} {
724            molinfo top set rotate_matrix [list $item(rotate)]
725            # send rotation matrix back to the client so we can pause later
726            server_send_result $client [list nv>rotatemtx $item(num) $item(rotate)]
727        } else {
728            puts "ERROR: bad work frame: [array get item]"
729        }
730
731        # flag to use the stored default view? then set that
732        if {[info exists item(defview)] && $item(defview)} {
733            if {[info exists Views($item(frame))]} {
734                eval molinfo top set $Views($item(frame))
735            }
736        }
737        catch {unset Work($item(num))}
738        break
739    }
740
741    # force VMD to update and grab the screen
742    display update
743    tkrender SnapShot
744
745    set data [SnapShot data -format PPM]
746    server_send_result $client "nv>image epoch $item(epoch) frame $item(num) length [string length $data]" $data
747
748    # if there's more work in the queue, try again later
749    if {[llength $Work(queue)] > 0} {
750        after 1 server_send_image
751    }
752}
753
754proc server_send_result {cout cmd {data ""}} {
755    global Epoch Sendqueue
756
757    # add this result to the output queue
758    # wait until the client is ready, then send the output
759    lappend Sendqueue [list epoch $Epoch cmd $cmd bytes $data]
760    fileevent $cout writable [list server_send $cout]
761}
762
763proc server_oops {cout mesg} {
764    # remove newlines -- all lines must start with nv>
765    set mesg [string map {\n " "} $mesg]
766    server_send_result $cout "nv>oops [list $mesg]"
767}
768
769if {$Paradigm eq "socket"} {
770    socket -server server_accept 2018
771} else {
772    fileevent stdin readable [list server_handle stdin stdout]
773
774    # identify server type to this client
775    puts stdout "vmd 0.1"
776    flush stdout
777    fconfigure stdout -buffering none -blocking 0
778}
779
780# vmd automatically drops into an event loop at this point...
781#
782# The VMD TCL interpreter is by default interactive.  Their version
783# of tkconsole always turns this on.  Turn this off
784# so that unknown commands like "scene" don't get exec-ed.
785set ::tcl_interactive 0
Note: See TracBrowser for help on using the repository browser.